React + TypeScript 实现泛型组件
泛型類型TypeScript 中,類型(interface, type)是可以聲明成泛型的,這很常見。 interface Props<T> {content: T; }這表明 Props 接口定義了這么一種類型:
或者,TypeScript 能夠跟使用時候提供的值自動推斷出類型 T,無需顯式指定: 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" });上面因為 Foo 函數接收 Props<T> 作為入參,意味著我們在調用 Foo 的時候需要傳遞類型 T 以確定 Props<T>,所以 Foo 函數也變成了泛型。 當調用 Foo({ content: 42 }) 的時候,TypeScript 自動解析出 T 為 number,此時對應的函數簽名為: function Foo<number>(props: Props<number>): void;而我們并沒有顯式地指定其中的類型 T,像這樣 Foo<number>({ content: 42 });。 泛型組件將上面的 Foo 函數返回 JSX 元素,就成了一個 React 組件。因為它是泛型函數,它所形成的組件也就成了 泛型組件/Generic Components。 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 可根據傳入的實際值解析泛型類型,所以 <Foo<string> content={"hello"}></Foo> 中 string 是可選的,這里只為展示,讓你看到其實 React 組件還可以這么玩。 為了進一步理解泛型組件,再看下非泛型情況下上面的組件是長怎樣的。 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 組件常規的寫法。它定義的入參 Props 只接收 string 類型。由此也看出泛型的優勢,即大部分代碼可復用的情況下,將參數變成泛型后,不同類型的入參可復用同一組件,不用為新類型新寫一個組件。 除了函數組件,對于類類型的組件來說,也是一樣可泛型化的。 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>); };一個更加真實的示例一個更加實用的示例是列表組件。列表中的分頁加載,滾動刷新邏輯等,對于所有列表數據都是通用的,將這個列表組件書寫成泛型便可和任意類型列表數據結合,而無須通過其他方式來達到復用的目的,將列表元素聲明成 any 或 Record<string,any> 等類型。 先看不使用泛型情況下,如何實現這么一個列表組件。此處只看列表元素的展示以闡述泛型的作用,其他邏輯比如數據加載等先忽略。 列表組件 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>; }上面,為了盡可能滿足大部分數據類型,將列表的元素類型定義成了 [prop: string]: any; 的形式,其實和 Record<string,any> 沒差。在這里已經可以看到類型的丟失了,因為出現了 any,而我們使用 TypeScript 的首要準則是盡量避免 any。 然后是使用上面所定義的列表組件: 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>); };這里使用時,item.name 的類型已經成了 any。對于簡單數據來說,還可以接收這樣類型的丟失,但對于復雜類型,類型的丟失就完全享受不到 TypeScript 所帶來的類型便利了。 上面的實現還有個問題是它規定了列表元素必需是對象,理所應當地就不能處理元始類型數組了,比如無法渲染 ['wayou','niuwayong'] 這樣的輸入。 下面使用泛型改造上面的列表組件,讓它支持外部傳入類型。 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>; }改造后,列表元素的類型完全由使用的地方決定,作為列表組件,內部它無須關心,同時對于外部傳遞的 children 回調中 item 入參,類型也沒有丟失。 使用改造后的泛型列表: 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>); }; |
轉載于:https://www.cnblogs.com/Wayou/p/react_typescript_generic_components.html
總結
以上是生活随笔為你收集整理的React + TypeScript 实现泛型组件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: git clone 仓库的部分代码
- 下一篇: 域渗透基础之环境搭建(单域到组件域林)