reactjs 如何在React中的props中使用泛型?

cqoc49vn  于 2023-04-11  发布在  React
关注(0)|答案(8)|浏览(171)

在一个基于类的组件中,我可以很容易地写一些这样的代码:

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中使用泛型,有什么建议吗?

uurv41yg

uurv41yg1#

你不能创建一个带有类型注解的函数组件,并使其成为泛型。所以这将不起作用,因为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'),
)
4c8rllxm

4c8rllxm2#

类型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>
}

然后使用类型化组件作为<Example<string> value="foo"/>

k4aesqcs

k4aesqcs3#

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

e5njpo684#

在处理功能组件之前,我假设原始代码示例缺少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}]}
  ...
/>
)

好了,现在你可以用一个通用的 prop 来创建一个功能组件。
你会被“现代”语法卡住,因为它使用了一个赋值和箭头函数,这对你的通用情况毫无用处:

// 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之前为我们做的那样。

tnkciper

tnkciper5#

React 18,Typescript 4.6.3

interface IProps<T> {
  data: T[];
}
export const YourComponent = <T,>(props: IProps<T>) => {}
sy5wg1nm

sy5wg1nm6#

补充#1。
如果要将组件导出为FunctionComponent并传递eslint displayName错误。
你可以做它波纹管。

const yourComponentWithLowerCase: <T>(props: PropsWithChildren<Props<T>>) => ReactElement | null = (props) => {
  // code
}

export const YourComponentWithUpperCase = yourComponentWithLowerCase;
(YourComponentWithUpperCase as FunctionComponent).displayName = 'something'
w6mmgewl

w6mmgewl7#

This answer是一个很好的例子,因为它正确地定义了props和函数的返回类型。
作为替代方案,这些可以被定义为函数而不是箭头函数。这避免了扩展prop类型以提示TS编译器这不是一个react组件的需要。

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

oxf4rvwz8#

我使用React Native创建了一个简单的自定义按钮组件,展示了在React Functional Component中使用泛型和props的场景。

import React from 'react';
import { Pressable, StyleSheet, Text } from 'react-native';

export type ButtonType = 'filled' | 'outline';

interface IButtonProps<T extends ButtonType> {
  type: T;
  onPress: () => void;
  filledText?: T extends 'filled' ? string : never;
  outlineText?: T extends 'outline' ? string : never;
}

const CustomButton = <T extends ButtonType>({
  type,
  filledText,
  outlineText,
  onPress,
}: IButtonProps<T>) => {
  return (
    <Pressable
      style={
        type === 'filled' ? styles.filledContainer : styles.outlineContainer
      }
      onPress={onPress}>
      <Text style={styles.textContainer}>{filledText || outlineText}</Text>
    </Pressable>
  );
};

export default CustomButton;

export const styles = StyleSheet.create({
  filledContainer: {
    flex: 1,
    padding: 16,
    backgroundColor: '#0096FF',
    borderRadius: 10,
  },
  outlineContainer: {
    flex: 1,
    padding: 16,
    borderWidth: 1,
    borderColor: '#0096FF',
    borderRadius: 10,
  },
  textContainer: {
    color: '#191970',
    textAlign: 'center',
    fontWeight: '500',
    fontSize: 22,
  },
});
  • 对于React.js用户,您只需相应地更改上述代码片段的JSX。*

让我们通过设置一个类型来调用自定义按钮组件,该类型将有条件地只接受与特定类型相关的属性,否则将显示类型错误。

<CustomButton
  type="filled"
  onPress={() => Alert.alert('Pressed')}
  filledText="Filled"
 />

<CustomButton
   type="outline"
   onPress={() => Alert.alert('Pressed')}
   outlineText="Outline"
  />

相关问题