介紹
在構(gòu)建React應(yīng)用程序時,通常需要在組件之間共享某些組件邏輯。 React可以使用許多模式來實(shí)現(xiàn)這一目標(biāo),其中最先進(jìn)的,也是最受歡迎的模式是高階組件。 本指南將說明如何使用Typescript語言在React中使用高階組件來確保某些類型的安全性。
什么是高階組件?
高階組件類似于在函數(shù)編程中廣泛使用的高階函數(shù)模式。
盡可能簡單地說,高階組件是一個將組件作為參數(shù)并返回新組件的函數(shù)。 該函數(shù)應(yīng)該是一個純函數(shù),因?yàn)樗粫薷膫鬟f的組件并且沒有其他副作用,并且通常會將傳遞的組件包裝在另一個組件中以添加一些行為或注入一些預(yù)期的數(shù)據(jù),或者有時兩者。
可以在此處找到更完整的React高階組件描述。
React組件
本指南將從包含標(biāo)題,正文和頁腳的React頁面開始,該頁面將在掛載時進(jìn)行API調(diào)用以檢索某些數(shù)據(jù)并將其顯示在正文中。 然后,這將用于創(chuàng)建兩個高階組件,一個將在頁眉和頁腳中顯示一個組件,另一個將從API讀取數(shù)據(jù)并將數(shù)據(jù)注入組件的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>
</>);
}
}
使用高階組件
首先要做的是創(chuàng)建一個新的高階組件,它將一個組件顯示在頁面的正文部分,將該組件放在一個<div>中,并提供頁眉和頁腳元素。
此函數(shù)的簽名如下所示:
function withHeaderAndFooter<T>(Component: React.ComponentType<T>)
在這個簽名中,<T>是一個Typescript泛型類型。在這種情況下,T表示在渲染高階組件時傳遞的組件props的類型,并且由于沒有注入新的props,返回的組件應(yīng)該擁有與原版相同類型的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組件相同的方式呈現(xiàn)頁眉和頁腳,并在正文<div>中呈現(xiàn)傳遞的組件。傳遞到高階組件的props使用對象擴(kuò)展運(yùn)算符傳遞到此組件中,如此{ ...props}。
在高階組件中注入props
將props注入組件可能是更高階組件的更常用的用例。
原始react組件調(diào)用API來獲取一些數(shù)據(jù)然后呈現(xiàn)它。這種行為也可以被提取到一個高階組件中,該組件將在掛載時調(diào)用API并通過其props將數(shù)據(jù)傳遞到提供的組件中。
首先要做的是定義一個將數(shù)據(jù)描述為prop的接口:
interface ThingsProps {
things: string[];
}
然后函數(shù)簽名將如下所示:
function withThings<T extends ThingsProps>(Component: React.ComponentType<T>)
在這種情況下,簽名使用泛型類型T指定props類型并擴(kuò)展ThingsProps接口,這意味著傳遞給此函數(shù)的任何組件必須在其props中實(shí)現(xiàn)該接口。
因?yàn)?code>thingsprop是由高階組件注入的,所以直接返回props的組件是不正確的,因?yàn)榻M件的任何使用者都需要包含ThingsProps, 解決此問題的一種方法是使用Omit運(yùn)算符,它接受一個類型T并從中刪除ThingsProps類型中存在的任何屬性。然后,這個高階組件可以返回一個類型為Omit <T,ThingsProps>的props組件,使用者不需要提供ThingsProps,從而避免了Typescript編譯器將拋出錯誤。
該函數(shù)的代碼在這里:
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也設(shè)置為things的當(dāng)前值,因此在使用這個組件時thing這個屬性將使用API調(diào)用中的數(shù)據(jù)賦值。在這種情況下,必須將this.props強(qiáng)制轉(zhuǎn)換為類型T,否則,Typescript編譯器將拋出錯誤。
使用高階的組件
正如預(yù)期的那樣,使用這些新的高階組件就是一個用現(xiàn)有組件調(diào)用該函數(shù)的情況,該組件具有正確的props并呈現(xiàn)函數(shù)的結(jié)果。 所以withHeaderAndFooter高階組件可以像這樣使用:
function helloWorld() {
return <div>Hello world</div>;
}
const HelloWorldPage = withHeaderAndFooter(helloWorld);
return <HelloWorldPage />;
HelloWorldPage組件現(xiàn)在由標(biāo)題,正文和頁腳組成,正文中顯示文本“Hello world”。
將helloWorld組件傳遞給withThings高階組件將導(dǎo)致Typescript編譯器出錯,因?yàn)?code>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 />;
正如在創(chuàng)建withThings組件時所討論的那樣,在渲染時不需要傳遞thing prop,因?yàn)檫@是由高階組件處理的。
創(chuàng)建一個由withHeaderAndFooter和withThings高階組件組成的組件只是將結(jié)果從一個傳遞到另一個的問題。因此,創(chuàng)建一個組件,用于將頁眉,頁腳和正文中的helloThings組件包裝在頁面中還注入了thing,可以這樣做:
const HelloThingsPage = withThings(helloThings);
const FullPage = withHeaderAndFooter(HelloThingsPage);
或者像這樣組成一行:
const FullPage = withHeaderAndFooter(withThings(helloThings));
使用<FullPage />生成的FullPage組件現(xiàn)在與原始組件相同。
調(diào)用高階組件的順序沒有區(qū)別,withThings(withHeaderAndFooter(helloThings))可以實(shí)現(xiàn)相同的結(jié)果。
參考
ts-higher-order-components
Higher Order Composition with Typescript for React
TypeScript 高級技巧