介绍
在构建React应用程序时,通常需要在组件之间共享某些组件逻辑。 React可以使用许多模式来实现这一目标,其中最先进的,也是最受欢迎的模式是高阶组件。 本指南将说明如何使用Typescript语言在React中使用高阶组件来确保某些类型的安全性。
什么是高阶组件?
高阶组件类似于在函数编程中广泛使用的高阶函数模式。
尽可能简单地说,高阶组件是一个将组件作为参数并返回新组件的函数。 该函数应该是一个纯函数,因为它不会修改传递的组件并且没有其他副作用,并且通常会将传递的组件包装在另一个组件中以添加一些行为或注入一些预期的数据,或者有时两者。
可以在此处找到更完整的React高阶组件描述。
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>
</>);
}
此组件以与原react组件相同的方式呈现页眉和页脚,并在正文<div>中呈现传递的组件。传递到高阶组件的props使用对象扩展运算符传递到此组件中,如此{ ...props}
。
在高阶组件中注入props
将props注入组件可能是更高阶组件的更常用的用例。
原始react组件调用API来获取一些数据然后呈现它。这种行为也可以被提取到一个高阶组件中,该组件将在挂载时调用API并通过其props将数据传递到提供的组件中。
首先要做的是定义一个将数据描述为prop的接口:
interface ThingsProps {
things: string[];
}
然后函数签名将如下所示:
function withThings<T extends ThingsProps>(Component: React.ComponentType<T>)
在这种情况下,签名使用泛型类型T指定props类型并扩展ThingsProps接口,这意味着传递给此函数的任何组件必须在其props中实现该接口。
因为things
prop是由高阶组件注入的,所以直接返回props的组件是不正确的,因为组件的任何使用者都需要包含ThingsProps
, 解决此问题的一种方法是使用Omit
运算符,它接受一个类型T并从中删除ThingsProps
类型中存在的任何属性。然后,这个高阶组件可以返回一个类型为Omit <T,ThingsProps>
的props组件,使用者不需要提供ThingsProps
,从而避免了Typescript编译器将抛出错误。
该函数的代码在这里:
function withThings<T extends ThingsProps>(Component: React.ComponentType<T>) {
return (props)<Omit<T, keyof ThingsProps>> => {
const [things, setThings] = React.useState([] as string[]);
const fetchThings = async () => {
const things = await getThings();
setThings(things);
}
React.useEffect(() => {
fetchThings()
}, []);
render() {
return <Component {...this.props as T} things={things} />;
}
};
}
渲染此组件时,使用{ ... this.props }
将props
传递到高阶组件,同时thing
这个属性prop
也设置为things
的当前值,因此在使用这个组件时thing
这个属性将使用API调用中的数据赋值。在这种情况下,必须将this.props
强制转换为类型T,否则,Typescript编译器将抛出错误。
使用高阶的组件
正如预期的那样,使用这些新的高阶组件就是一个用现有组件调用该函数的情况,该组件具有正确的props
并呈现函数的结果。 所以withHeaderAndFoote
r高阶组件可以像这样使用:
function helloWorld() {
return <div>Hello world</div>;
}
const HelloWorldPage = withHeaderAndFooter(helloWorld);
return <HelloWorldPage />;
HelloWorldPage
组件现在由标题,正文和页脚组成,正文中显示文本“Hello world”。
将helloWorld
组件传递给withThings
高阶组件将导致Typescript编译器出错,因为withThings
需要一个具有ThingsProps
定义thing
属性的组件。这个高阶组件可以像这样使用:
function helloThings(props: ThingsProps) {
return (
<ul>
{props.things.map(thing => (
<li key={thing}>{thing}</li>
))}
</ul>);
}
const HelloThingsPage = withThings(helloThings);
return <HelloThingsPage />;
正如在创建withThings
组件时所讨论的那样,在渲染时不需要传递thing prop
,因为这是由高阶组件处理的。
创建一个由withHeaderAndFooter
和withThings
高阶组件组成的组件只是将结果从一个传递到另一个的问题。因此,创建一个组件,用于将页眉,页脚和正文中的helloThings
组件包装在页面中还注入了thing
,可以这样做:
const HelloThingsPage = withThings(helloThings);
const FullPage = withHeaderAndFooter(HelloThingsPage);
或者像这样组成一行:
const FullPage = withHeaderAndFooter(withThings(helloThings));
使用<FullPage />
生成的FullPage
组件现在与原始组件相同。
调用高阶组件的顺序没有区别,withThings(withHeaderAndFooter(helloThings))
可以实现相同的结果。
参考
ts-higher-order-components
Higher Order Composition with Typescript for React
TypeScript 高级技巧
网友评论