使用 Typescript 进行 React 的高阶组合
介绍
在构建 React 应用程序时,通常需要跨组件共享某些组件逻辑。React 中可以采用多种模式来实现这一点,其中最先进、最流行的一种是高阶组件。本指南将展示如何使用 Typescript 语言在 React 中使用高阶组件来确保某种类型安全。
什么是高阶组件?
完整组件
本指南将从一个包含页眉、正文和页脚的 React 页面开始,该页面将在挂载时进行 API 调用以检索一些数据并将其显示在正文中。然后,这将用于创建两个高阶组件,一个将在页眉和页脚内显示组件,另一个将从 API 读取数据并将数据注入组件的 props。
初始组件的代码在这里:
class Page extends React.Component {
state = { things: [] as string[] };
async componentDidMount() {
const things = await getThings();
this.setState({ things });
}
render() {
return (
<>
<header className="app-header">
....
</header>
<div className="app-body">
<ul>
{this.state.things.map(thing => (
<li key={thing}>{thing}</li>
))}
</ul>
</div>
<footer className="app-footer">
...
</footer>
</>);
}
}
包装器高阶组件
要做的第一件事是创建一个新的高阶组件,该组件将获取要在页面正文部分显示的组件,将该组件放在正文<div>内,并提供页眉和页脚元素。
该函数的签名如下:
function withHeaderAndFooter<T>(Component: React.ComponentType<T>)
此签名中使用的<T>是 Typescript泛型类型。在这种情况下,T表示在渲染高阶组件时传递的组件 props 的类型,并且由于未注入任何 props,因此返回的组件应具有与原始组件相同类型的 props。完整函数的代码如下:
function withHeaderAndFooter<T>(Component: React.ComponentType<T>) {
return (props: T) => (
<>
<header className="app-header">
...
</header>
<div className="app-body">
<Component {...props} />
</div>
<footer className="app-footer">
...
</footer>
</>);
}
该组件以与整页组件相同的方式渲染页眉和页脚,并在主体<div>内渲染传递的组件。传递到高阶组件的 props 使用对象展开运算符传递到此组件中,就像这样{...props}。
在高阶组件中注入 Props
对于高阶组件来说,将 props 注入到组件中可能是更流行的用例。
原始的整页组件调用一个API来获取一些数据,然后呈现它。这个行为也可以提取到高阶组件中,该组件将在挂载时进行API调用,并通过其props将数据传递到提供的组件中。
首先要做的是定义一个将数据描述为 prop 的接口:
interface ThingsProps {
things: string[];
}
函数签名将如下所示:
function withThings<T extends ThingsProps>(Component: React.ComponentType<T>)
在这种情况下,签名指定 props(泛型类型T)扩展了ThingsProps接口,这意味着传递到此函数的任何组件都必须在其 props 中实现该接口。
因为things prop 要由高阶组件注入,所以返回一个接受传入的相同 props 的组件(如上面的包装器示例)是不正确的,因为组件的任何消费者都需要包含一个things prop。解决这个问题的一种方法是使用实用程序类型包。这个包包含一个Subtract<T, T1>运算符,它接受类型T并从中删除类型T1中存在的任何属性。这个高阶组件可以返回一个具有Subtract<T, ThingsProps>类型 props 的组件,这意味着消费者不需要提供things prop 来满足 Typescript 编译器的要求。
该函数的代码如下:
function withThings<T extends ThingsProps>(Component: React.ComponentType<T>) {
return class extends React.Component<Subtract<T, ThingsProps>> {
state = { things: [] as string[] };
async componentDidMount() {
const things = await getThings();
this.setState({ things });
}
render() {
return <Component {...this.props as T} things={this.state.things} />;
}
};
}
header 和 footer 函数返回的是功能组件,而此组件需要状态,因此返回的是类组件。状态定义和componentDidMount方法与原始全页组件中的相同。渲染此组件时,以及使用{...this.props}将所有传递到高阶组件的 props 时,things prop 也会设置为this.state.things的当前值,因此使用此组件时,things prop 将使用来自 API 调用的数据进行填充。在这种情况下,this.props必须转换为类型T,否则,Typescript 编译器将抛出错误。
使用高阶组件
正如预期的那样,使用这些新的高阶组件就是使用具有正确 props 的现有组件调用函数并呈现函数结果的情况。因此,withHeaderAndFooter高阶组件可以这样使用:
function helloWorld() {
return <div>Hello world</div>;
}
const HelloWorldPage = withHeaderAndFooter(helloWorld);
return <HelloWorldPage />;
HelloWorldPage组件现在由页眉、正文和页脚组成,其中正文中显示文本“Hello world”。
将helloWorld组件传递给withThings高阶组件将导致 Typescript 编译器出错,因为withThings需要一个带有things prop 的组件。这个高阶组件可以像这样使用:
function helloThings(props: ThingsProps) {
return (
<ul>
{props.things.map(thing => (
<li key={thing}>{thing}</li>
))}
</ul>);
}
const HelloThingsPage = withThings(helloThings);
return <HelloThingsPage />;
正如创建withThings组件时所讨论的那样,渲染时无需传递things prop,因为这由高阶组件负责。
要创建一个由withHeaderAndFooter和withThings高阶组件组成的组件,只需将结果从一个组件传递到另一个组件即可。因此,创建一个将helloThings组件包装在带有页眉、页脚和正文的页面内的组件,并注入内容,可以像这样完成:
const HelloThingsPage = withThings(helloThings);
const FullPage = withHeaderAndFooter(HelloThingsPage);
或者像这样写成一行:
const FullPage = withHeaderAndFooter(withThings(helloThings));
并渲染<FullPage />。生成的FullPage组件现在与原始组件相同。
调用高阶组件的顺序没有区别,通过withThings(withHeaderAndFooter(helloThings))可以达到相同的结果。
结论
通过使用高阶组件模式组合新组件,可以实现 React 中组件行为的重用。本指南提供了一些如何使用 Typescript 实现此模式的示例,您可以在此处找到使用本指南中的高阶组件的示例应用程序。
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~