web-dev-qa-db-ja.com

子がReactノードまたは関数のいずれかである場合にReact.FC <props>型を使用する方法

このサンプルコンポーネントがあります

_import React, { FC, ReactNode, useMemo } from "react";
import PropTypes from "prop-types";

type Props = {
  children: ((x: number) => ReactNode) | ReactNode;
};

const Comp: FC<Props> = function Comp(props) {
  const val = useMemo(() => {
    return 1;
  }, []);

  return (
    <div>
      {typeof props.children === "function"
        ? props.children(val)
        : props.children}
    </div>
  );
};

Comp.propTypes = {
  children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired
};

export default Comp;
_

ここでの私の意図は、コンポーネントのchildrenプロパティが

  • node、これは

    レンダリングできるもの:数値、文字列、要素、またはこれらのタイプを含む配列(またはフラグメント)。

  • function、(または「レンダープロップ」)コンポーネント内部から値を取得し、別のnodeを返すだけ

ここでのポイントは明示的です。childrenは、1つ(node、ほとんどすべてです)またはもう1つ(単にfunctionです)のいずれかです。

問題

型チェックで次の問題に直面しています。

  • ここに示すコードをそのままにしておくと、? props.children(val) 行に次のエラーメッセージが表示されます。

    この式は呼び出すことができません。タイプ 'Function | ((x:数値)=> ReactNode)| (文字列&{})| (番号&{})| (false&{})| (真&{})| ({}&文字列)| ({}&番号)| ({}&false)| ({}&true)| (((x:数値)=> ReactNode)&文字列)

このエラーは理解できません。

  • Propsタイプを次のように変更した場合
_type Props = {
  children: (x: number) => ReactNode;
};
_

そして、React自身の_type PropsWithChildren<P> = P & { children?: ReactNode };_に依存して、childrenが関数ではない場合を処理すると、エラーが発生します

(プロパティ)子ですか?:PropTypes.Validator <(x:number)=> React.ReactNode>タイプ 'Validator'はタイプ 'Validator <(x:number)=> ReactNode>'に割り当てられません。タイプ 'ReactNodeLike'はタイプ '(x:number)=> ReactNode'に割り当てることができません。タイプ 'string'はタイプ '(x:number)=> ReactNode'.ts(2322)に割り当て可能ではありませんComp.tsx(5、3):予期されるタイプは、タイプでここで宣言されているプロパティ' children 'から取得されます

children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired

唯一の解決策は、Propsタイプをそのままにすることです

_type Props = {
  children: (x: number) => ReactNode;
};
_

また、_Comp.propTypes_を_children: PropTypes.func.isRequired_に変更します。これは、明示的にしたいのでnotです。

質問

この質問の最初に示したように、コードを明示的にして、型チェックでエラーがスローされないようにするにはどうすればよいですか?

CodeSandboxリンク

グローバルユニオンが役立つと思います。

type Props = {
  children: ((x: number) => ReactNode);
} | {
  children: ReactNode;
};
1
Andrey

機能し、Props宣言を別の方法で記述したり、他の何かを別の方法で書き換えたりする必要のない別の解決策は、コンポーネントの定義中にpropsパラメーターの型を厳密に定義することです 、 このような

type Props = {
  children: ((x: number) => ReactNode) | ReactNode;
};

const Comp: FC<Props> = function Comp(props: Props) { // we strictly define the props type here
  ...
}

Comp.propTypes = {
  children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired
};

これが違いを生む理由は100%わかりません。自分の直感では、独自のProps定義を型チェッカーに「強制」するため、可能なスコープを制限しています。

更新

元の質問をして以来、私は最終的に自分の問題の次の解決策に落ち着きました:私は自分の関数コンポーネントタイプを定義しました

//global.d.ts

declare module 'react' {
  // Do not arbitrarily pass children down to props.
  // Do not type check actual propTypes because they cannot always map 1:1 with TS types,
  // forcing you to go with PropTypes.any very often, in order for the TS compiler
  // to shut up

  type CFC<P = {}> = CustomFunctionComponent<P>;

  interface CustomFunctionComponent<P = {}> {
    (props: P, context?: any): ReactElement | null;
    propTypes?: { [key: string]: any };
    contextTypes?: ValidationMap<any>;
    defaultProps?: Partial<P>;
    displayName?: string;
  }
}

このソリューション

  • 関数コンポーネントとは何かを厳密に定義できます
  • 任意のchildrenプロップを私の定義に強制しません
  • 実際のComponent.propTypesとTS type Props = {...}を相互参照しません。多くの場合、それらは正確に1:1にマップされず、PropTypes.anyを使用せざるを得ませんでした。

TSタイプとともにComponent.propTypesを保持している理由は、開発中にTSが非常に優れている一方で、ランタイム中にタイプが正しくない場合にPropTypesが実際に警告するためです。 、APIレスポンスのフィールドは数値であるはずでしたが、現在は文字列になっています。このようなことが起こる可能性があり、TSが支援できるものではありません。

参考文献

https://github.com/DefinitelyTyped/DefinitelyTyped/issues/34237https://github.com/DefinitelyTyped/DefinitelyTyped/issues/34237#issuecomment-486374424