かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

今更の React v18 : children の扱いが変わった (TypeScript)

ようやく React v18 のキャッチアップを始めたのでメモ。

React.FunctionComponent (FC), React.Component に暗黙的に含まれていた children が除かれた

children を props として受け取るには明示的に children: ReactNode のように型定義をする必要がある

// Before
const MyComponent: FC = (props) => {
  return (
    <div>{props.children}</div>
  );
};

// After
type MyComponenProps = {
  children: React.ReactNode;
};

const MyComponent: FC<MyComponenProps> = (props) => {
  return (
    <div>{props.children}</div>
  );
};

受け取る props に children が定義されてない場合は Type Error になる

import { FC } from 'react';

type MyComponenProps = {};

const MyComponent: FC<MyComponenProps> = (props) => {
  return (
    <div>{props.children}</div>
  );
};

=> Property 'children' does not exist on type 'MyComponent'.ts (2339)

cf.

React.VFC が非推奨になった

React.FC から children を除いた React.VFC がありましたが、React v18 からは React.FC と同じになったので deprecated になっていました。

// React.FunctionComponent
type FC<P = {}> = FunctionComponent<P>;
interface FunctionComponent<P = {}> {
  (props: P, context?: any): ReactElement<any, any> | null;
  propTypes?: WeakValidationMap<P> | undefined;
  contextTypes?: ValidationMap<any> | undefined;
  defaultProps?: Partial<P> | undefined;
  displayName?: string | undefined;
}

// @deprecated - Equivalent with `React.FC`.
type VFC<P = {}> = VoidFunctionComponent<P>;
interface VoidFunctionComponent<P = {}> {
  (props: P, context?: any): ReactElement<any, any> | null;
  propTypes?: WeakValidationMap<P> | undefined;
  contextTypes?: ValidationMap<any> | undefined;
  defaultProps?: Partial<P> | undefined;
  displayName?: string | undefined;
}

React.FC or JSX.Element ?

React.VFC は過渡期で React.FC も変更になる可能性があるので JSX.Element を使っておくのが良いのでは?という話題がありました。React v18 になって React.VFC が非推奨になり React.FC から暗黙的な children が除かれた (以前の React.VFC と同じになった) ので JSX.ElementReact.FC どちらが良いのでしょう?

型の違い

JSX.Element

namespace JSX {
  interface Element extends React.ReactElement<any, any> { }

React.FC

type FC<P = {}> = FunctionComponent<P>;
interface FunctionComponent<P = {}> {
  (props: P, context?: any): ReactElement<any, any> | null;
  propTypes?: WeakValidationMap<P> | undefined;
  contextTypes?: ValidationMap<any> | undefined;
  defaultProps?: Partial<P> | undefined;
  displayName?: string | undefined;
}
React.FC と JSX.Element の違い
  • React.FCJSX.Element 又は null を返す事ができる
  • React.FCpropTypes, contextTypes, defaultProps, displayName のフィールドを持っている
propTypes

React は組み込みの型チェック機能
cf. PropTypes を用いた型チェック – React

=> TypeScript を使用する場合は使うことはなさそう

contextTypes

propTypes と同様、コンテクストで受け取る値の型定義をTypescriptなどを使わずに定義する手段のようです。
cf. 【検証】React.FC と React.VFC はべつに使わなくていい説 – KRAY Inc., Legacy Context – React

=> TypeScript を使用する場合は使う必要はなさそう

defaultProps

props にデフォルト値を設定するためのフィールド
FunctionComponent では defaultProps を使わなくてもデフォルト値を設定できる
また defaultProps 自体が非推奨になっているので使う必然は無さそう

cf. `defaultProps` rule to be deprecated on function components · Issue #2396 · jsx-eslint/eslint-plugin-react · GitHub

displayName

displayName 文字列はデバッグメッセージに使用されます。通常、コンポーネントを定義する関数またはクラスの名前から推測されるため、明示的に設定する必要はありません。
cf. React.Component – React

=> デバック用のフィールドで明示的に設定する必要はないとドキュメントに書かれているので使うことは無さそう

React.FC と JSX.Element どちらを使うのかの個人的な結論

React.FCJSX.Elementnull を返せるという型という認識で良さそう。
null を含むかどうかの違いしかないので、 React.FC, JSX.Element プロジェクト・チームで統一されるならどちらでも良いのでは?と感じました。

個人的な好みでは props の型をジェネリクスで渡せるので React.FC の方が好みです。特に props の型名が長くなると Reat.FC の方がコンポーネント名直後に型がまとまるので見通しが良いように感じます。

type MyComponentProps = {
  onClick: () => void;
  children: React.ReactNode;
}

// JSX.Element
const MyComponentJSXElement = ({ onClick, children }: MyComponentProps): JSX.Element | null => {
  // … 
};

// React.FC
import { FC } from 'react';

const MyComponentFC: FC<MyComponentProps> = ({ onClick, children }) => {
  // ...
}

cf.

まとめ

  • React v18 では暗黙的に props に含まれていた children は、使用するなら明示的に props の型を定義しなければならなくなった
    • children の型が定義されてない状態で props.children を使用するとエラーになる
    • children の型は React.ReactNode を使えばOK
  • React.VFCReact.FC と同じになったので非推奨になった

[参考]

基礎から学ぶ React/React Hooks

基礎から学ぶ React/React Hooks

Amazon