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> ); };
|