React + TypeScript 實現泛型組件
- 2019 年 10 月 3 日
- 筆記
泛型類型TypeScript 中,類型(interface, type)是可以聲明成泛型的,這很常見。 interface Props<T> { content: T; }
這表明
type StringProps = Props<string>; let props: StringProps; props = { // ? Type 'number' is not assignable to type 'string'.ts(2322) content: 42 }; props = { // ✅ content: "hello" };
或者,TypeScript 能夠跟使用時候提供的值自動推斷出類型 interface Props<T> { content: T; } function Foo<T>(props: Props<T>) { console.log(props); } /** 此時 Foo 的完整簽名為: function Foo<number>(props: Props<number>): void */ Foo({ content: 42 }); /** 此時 Foo 的完整簽名為: function Foo<string>(props: Props<string>): void */ Foo({ content: "hello" });
上面因為 當調用 function Foo<number>(props: Props<number>): void;
而我們並沒有顯式地指定其中的類型 泛型組件將上面的 function Foo<T>(props: Props<T>) { return <div> {props.content}</div>; } const App = () => { return ( <div className="App"> <Foo content={42}></Foo> <Foo<string> content={"hello"}></Foo> </div> ); };
一如上面的討論,因為 TypeScript 可根據傳入的實際值解析泛型類型,所以 為了進一步理解泛型組件,再看下非泛型情況下上面的組件是長怎樣的。 interface Props { content: string; } function Foo(props: Props) { return <div>{props.content}</div>; } const App = () => { return ( <div className="App"> {/* ? Type 'number' is not assignable to type 'string'.ts(2322) */} <Foo content={42}></Foo> <Foo content={"hello"}></Foo> </div> ); };
以上,便是一個 React 組件常規的寫法。它定義的入參 除了函數組件,對於類類型的組件來說,也是一樣可泛型化的。 interface Props<T> { content: T; } class Bar<T> extends React.Component<Props<T>> { render() { return <div>{this.props.content}</div>; } } const App = () => { return ( <div className="App"> <Bar content={42}></Bar> <Bar<string> content={"hello"}></Bar> </div> ); };
一個更加真實的示例一個更加實用的示例是列表組件。列表中的分頁加載,滾動刷新邏輯等,對於所有列表數據都是通用的,將這個列表組件書寫成泛型便可和任意類型列表數據結合,而無須通過其他方式來達到復用的目的,將列表元素聲明成 先看不使用泛型情況下,如何實現這麼一個列表組件。此處只看列表元素的展示以闡述泛型的作用,其他邏輯比如數據加載等先忽略。 列表組件 List.tsx interface Item { [prop: string]: any; } interface Props { list: Item[]; children: (item: Item, index: number) => React.ReactNode; } function List({ list, children }: Props) { // 列表中其他邏輯... return <div>{list.map(children)}</div>; }
上面,為了儘可能滿足大部分數據類型,將列表的元素類型定義成了 然後是使用上面所定義的列表組件: interface User { id: number; name: string; } const data: User[] = [ { id: 1, name: "wayou" }, { id: 1, name: "niuwayong" } ]; const App = () => { return ( <div className="App"> <List list={data}> {item => { // ? 此處 `item.name` 類型為 `any` return <div key={item.name}>{item.name}</div>; }} </List> </div> ); };
這裡使用時, 上面的實現還有個問題是它規定了列表元素必需是對象,理所應當地就不能處理元始類型數組了,比如無法渲染 下面使用泛型改造上面的列表組件,讓它支持外部傳入類型。 interface Props<T> { list: T[]; children: (item: T, index: number) => React.ReactNode; } function List<T>({ list, children }: Props<T>) { // 列表中其他邏輯... return <div>{list.map(children)}</div>; }
改造後,列表元素的類型完全由使用的地方決定,作為列表組件,內部它無須關心,同時對於外部傳遞的 使用改造後的泛型列表: interface User { id: number; name: string; } const data: User[] = [ { id: 1, name: "wayou" }, { id: 1, name: "niuwayong" } ]; const App = () => { return ( <div className="App"> <List list={data}> {item => { // ? 此處 `item` 類型為 `User` return <div key={item.name}>{item.name}</div>; }} </List> <List list={["wayou", "niuwayong"]}> {item => { // ? 此處 `item` 類型為 `string` return <div key={item}>{item}</div>; }} </List> </div> ); };
|