在react组件的props之间使用泛型

IT技术 reactjs typescript
2021-05-01 16:30:58

我正在尝试键入以下组件:

type Props<Data> = {
  data: Data[]
  render: ({ value }: { value: Data }) => React.ReactElement
}

const List: React.FC<Props> = ({data, render}) => {…}

我遇到的问题是我想要Data通用,无论你在数组中传入什么,我都想强类型渲染以知道value为 type Data问题是我不确定如何使用它。理想情况下,我想做以下事情:

<List data={[1,2,3]} render={(value) => <div>{value + 2}</div>}/>

并完成,在这种情况下,渲染会知道这value是一个数字。这可能吗?

2个回答

如果您尝试传递一组数字(从 推断data={1,2,3})。那么您只需要将 number 作为泛型类型传递给您的Props. 您需要做更多的映射才能使类型正确匹配。

功能组件方式

export interface ListProps<T = unknown> {
  data: T[];
  render: (item: T) => React.ReactElement;
}

export const GenericListItems = <T extends any>(): React.FC<ListProps<T>> => ({
  data,
  render
}): any => {
  return data.map(render);
};

强烈输入您的列表看起来像

const StringList = GenericListItems<string>();
const NumberList = GenericListItems<number>();

组件类方式

interface JsxClass<P, S> extends React.Component<P, S> {
  render(): React.ReactElement<P>[];
}

interface GenericComponentType<P, S> {
  new (props: P): JsxClass<P, S>;
}

interface ListProps<T> {
  data: T[];
  render: (value: T) => any;
}

class List<T> extends React.Component<ListProps<T>, {}> {
  render(): React.ReactElement<any>[] {
    return this.props.data.map(this.props.render);
  }
}

强烈输入您的列表看起来像

const NumberList: GenericComponentType<ListProps<number>, {}> = List;
const StringList: GenericComponentType<ListProps<string>, {}> = List;

这两个(组件类或功能组件)实例都可以以相同的方式使用。

<NumberList
  data={[1, 2, 3]}
  render={value => <div>{value + 2}</div>}
/>
<StringList
  data={["I", "Render", "Strings"]}
  render={value => <div>{value}</div>}
/>

如果您需要Data成为通用参数,那么您可以将其声明为组件定义。所以像class List<Data> extends React.Component<ListProps<Data>>or 之类的function List<Data>(props: ListProps<Data>),让typescript完成剩下的工作。

这是一个完整的代码示例:

import React from "react";

interface ListProps<Data> {
  data: Data[];
  render: (item: Data) => React.ReactNode;
}

function List<Data>({ data, render }: ListProps<Data>) {
  return <div>{data.map(render)}</div>;
}

const element1 = <List data={[1.234, 2.345, 3.456]} render={v => <span>{v.toFixed(2)}</span>} />;

const element2 = (
  <List data={["abc", "bca", "cab"]} render={v => <span>{v.substring(0, 1)}</span>} />
);

编辑

要强制数据为特定类型,您可以将泛型参数传递给 List,如下所示:

const element1 = (
  <List<number>
    // Typescript will complain:
    // Type 'string' is not assignable to type 'number'.ts(2322)
    data={["1.234", 2.345, 3.456]}
    render={v => <span>{v.toFixed(2)}</span>}
  />
);

在最后一个示例中,如果删除通用参数,则会收到另一个错误,因为数据将被推断为number | string

const element1 = (
  <List
    data={["1.234", 2.345, 3.456]}
    // Typescript will complain:
    //Property 'toFixed' does not exist on type 'ReactText'.
    //  Property 'toFixed' does not exist on type 'string'.ts(2339)
    // and `v` is infered to be (parameter) v: string | number
    render={v => <span>{v.toFixed(2)}</span>}
  />
);