【问题标题】:How to use generics in props in React in a functional component?如何在功能组件的 React 中使用 props 中的泛型?
【发布时间】:2021-10-21 10:00:14
【问题描述】:

在基于类的组件中,我可以轻松编写如下代码:

import * as React from 'react';
import { render } from 'react-dom';

interface IProps<T> {
    collapsed: boolean;
    listOfData: T[];
    displayData: (data: T, index: number) => React.ReactNode;
}

class CollapsableDataList<T> extends React.Component<IProps<T>> {
    render () {
        if (!this.props.collapsed) {
            return <span>total: {this.props.listOfData.length}</span>
        } else {
            return (
                <>
                    {
                        this.props.listOfData.map(this.props.displayData)
                    }
                </>
            )
        }
    }
}

render(
    <CollapsableDataList
        collapsed={false}
        listOfData={[{a: 1, b: 2}, {a: 3, b: 4}]}
        displayData={(data, index) => (<span key={index}>{data.a + data.b}</span>)}
    />,
    document.getElementById('root'),
)

其实这个CollapsableDataList组件应该是函数式组件,因为它是无状态的,但是我不知道如何编写函数式组件并在props中使用泛型,有什么建议吗?

【问题讨论】:

标签: reactjs typescript generics react-props


【解决方案1】:

您不能创建带有类型注释的功能组件并使其具有通用性。所以这不会起作用,因为T 没有定义,你不能在变量级别定义它:

const CollapsableDataList : React.FunctionComponent<IProps<T>> = p => { /*...*/ } 

但是,您可以跳过类型注释,将函数设为泛型并显式键入 props

import * as React from 'react';
import { render } from 'react-dom';

interface IProps<T> {
    collapsed: boolean;
    listOfData: T[];
    displayData: (data: T, index: number) => React.ReactNode;
}
const CollapsableDataList = <T extends object>(props: IProps<T> & { children?: ReactNode }) => {
    if (!props.collapsed) {
        return <span>total: {props.listOfData.length}</span>
    } else {
        return (
            <>
                {
                    props.listOfData.map(props.displayData)
                }
            </>
        )
    }
}


render(
    <CollapsableDataList
        collapsed={false}
        listOfData={[{a: 1, b: 2}, {a: 3, c: 4}]}
        displayData={(data, index) => (<span key={index}>{data.a + (data.b || 0)}</span>)}
    />,
    document.getElementById('root'),
)

【讨论】:

  • 我担心某些 HOC 要求您传递具有显式类型的组件(例如 ComponentClassFunctionalComponent 类型),然后那些没有类型注释的功能组件将不会t 通过类型检查。 (尚未测试)
  • @hronro Typescript 使用结构类型,因此函数的参数比声明的类型更重要。你仍然会遇到 HOC 的问题,因为 Typescript 没有更高阶的类型。因此,当您将类型参数传递给 HOC 时,它会丢失。但这是一个通用的通用组件问题,与其说是类型注释问题。
  • @AndyO 我刚刚从我认为的反应定义中复制了children 定义。没有必要尽可能添加泛型类型参数,T 将被推断出来。我总是更喜欢让编译器在可能的情况下进行推理。
  • @TitianCernicova-Dragomir 对,我错过了可以在这里推断出类型,谢谢! (我也喜欢尽量使用TS的推理能力)
  • 你可以使用 PropsWithChildren> 获取界面交集的装备
【解决方案2】:

在处理功能组件之前,我假设原始代码示例缺少 JSX 组件中的泛型,因为我没有看到它传递给 IProps 接口。即:

interface Ab {
  a: number;
  b: number;
}

...

// note passing the type <Ab> which will eventually make it to your IProps<T> interface and cascade the type for listOfData
return (
<CollapsableDataList<Ab>
  collapsed={false}
  listOfData={[{a: 1, b: 2}, {a: 3, c: 4}]}
  ...
/>
)

好的,现在只需稍加努力,您实际上就可以拥有一个带有通用 props 的功能组件。

你被困在使用“现代”语法中,因为它使用了一个赋值和箭头函数,这对你的一般情况没有用处:

// using this syntax there is no way to pass generic props
const CollapsableDataList: React.FC<IProps> = ({ collapsed, listOfData }) => {
  // logic etc.
  return (
  // JSX output
  );
}

让我们将变量赋值重写为旧的function

// we are now able to to write our function component with generics
function CollapsableDataList<T>({ collapsed, listOfData }: IProps<T> & { children?: React.ReactNode }): React.ReactElement {
  // logic etc.
  return (
  // JSX output
  );
}

如果组件不使用 children 道具,则不一定需要 children 解决方法,但我添加它是为了强调必须手动重新键入它的事实,就像 React.FC 之前为我们所做的那样。

【讨论】:

    【解决方案3】:

    React.FC 的类型本质上是这样的:

    <P = {}>(props: PropsWithChildren<P>, context?: any) => ReactElement | null
    

    所以而不是这个(这是不允许的):

    const Example: React.FC<Props<P>> = (props) => {
      // return a React element or null
    }
    

    你可以用这个:

    const Example = <P extends unknown>(props: PropsWithChildren<Props<P>>): ReactElement | null => {
      // return a React element or null
    }
    

    例如:

    const Example = <P extends unknown>({ value }: PropsWithChildren<{ value: P }>): ReactElement | null => {
      return <pre>{JSON.stringify(value)}</pre>
    }
    

    或者,更严格地说,如果组件不使用 children 属性并且不会返回 null

    const Example = <P>({ value }: { value: P }): ReactElement => {
      return <pre>{value}</pre>
    }
    

    然后将类型化组件用作&lt;Example&lt;string&gt; value="foo"/&gt;

    【讨论】:

      【解决方案4】:
      type Props<T> = {
          active: T;
          list: T[];
          onChange: (tab: T) => void;
      };
      
      export const Tabs = <T,>({ active, list, onChange }: Props<T>): JSX.Element => {
          return (
              <>
                  {list.map((tab) => (
                      <Button onClick={() => onChange(tab)} active={tab === active}>
                          {tab} 
                      </Button>
                  ))}
              </>
          );
      };
      

      【讨论】:

      • 请注意 内的悬空,这修复了编译器的错误并允许使用简单的泛型。
      【解决方案5】:

      #1 的补充。

      如果要将组件导出为 FunctionComponent 并传递 eslint displayName 错误。

      你可以这样做。

      const yourComponentWithLowerCase: <T>(props: PropsWithChildren<Props<T>>) => ReactElement | null = (props) => {
        // code
      }
      
      export const YourComponentWithUpperCase = yourComponentWithLowerCase;
      (YourComponentWithUpperCase as FunctionComponent).displayName = 'something'
      
      
      

      【讨论】:

        【解决方案6】:

        This answer 是一个很好的例子,因为它正确地定义了函数的 props 和返回类型。

        作为替代方案,这些可以定义为函数而不是箭头函数。这绕过了扩展 prop 类型以向 TS 编译器提示这不是 React 组件的需要。

        export function CollapsableDataList<T>(
          props: PropsWithChildren<IProps<T>>
        ): ReturnType<FunctionComponent<IProps<T>>> {
          // ...
        }
        

        【讨论】:

          猜你喜欢
          • 2023-04-04
          • 1970-01-01
          • 2020-03-25
          • 2021-06-29
          • 1970-01-01
          • 2020-10-09
          • 2021-10-15
          • 1970-01-01
          相关资源
          最近更新 更多