web-dev-qa-db-ja.com

機能コンポーネントのReactの小道具でジェネリックを使用する方法は?

クラスベースのコンポーネントでは、次のようなコードを簡単に記述できます。

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コンポーネントはステートレスであるため、機能コンポーネントである必要がありますが、関数コンポーネントを作成してプロップでジェネリックを使用する方法を理解できません。何かアドバイスはありますか?

19
hronro

タイプアノテーションを使用して機能コンポーネントを作成し、それをジェネリックにすることはできません。したがって、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'),
)

機能コンポーネントに取り組む前に、IPropsインターフェースに渡されていないので、元のコード例にはJSXコンポーネントのジェネリックがないと想定しています。 I. e .:

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}]}
  ...
/>
)

さて、少しの努力で、実際に一般的な小道具を備えた機能コンポーネントを持つことができます。

一般的な場合には役に立たない割り当てと矢印関数を使用しているため、「モダン」構文を使用してスタックしています。

// 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回避策は必ずしも必要ではありませんが、React.FCが以前に私たちのために手動で再入力する必要があるという事実を強調するために追加しました。

2
dmudro