まくろぐ

React の props.children の型定義には ReactNode を使う

更新:
作成:

children の型定義

TypeScript で React の関数コンポーネントを定義するときには、下記のような React.FC (React.FunctionComponent) を使用します。

type FC<P = {}> = FunctionComponent<P>;

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

// ...
type PropsWithChildren<P> = P & { children?: ReactNode | undefined };

FC の型パラメータ P は、上記のような PropsWithChildren 型にラップされるので、props の型定義をするときに明示的に children を含める必要はありません。 下記の ColorBox コンポーネントは、指定した背景色で子要素 (children) を表示します。

ColorBox.tsx
import { FC } from 'react'

type Props = {
  color: string
}

export const ColorBox: FC<Props> = ({ color, children }) => {
  return <div style={{ background: color }}>{children}</div>
}

ただ、これだと、ColorBox コンポーネントの型情報を見ただけでは、子要素 (children) が必要なのかどうかを判別することができません。 props の型定義で、明示的に children の型情報を指定するには、次のように React.ReactNode を使います。

import { FC, ReactNode } from 'react'

type Props = {
  color: string
  children: ReactNode
}

export const ColorBox: FC<Props> = ({ color, children }) => {
  return <div style={{ background: color }}>{children}</div>
}

これで、子要素を指定せずに ColorBox コンポーネントを使おうとしたときにエラー表示してくれるようになります。

<ColorBox color="red" />  // 子要素がないのでエラー!

ちなみに、子要素の指定がオプショナルであることを明示するには、次のように children? と定義すれば OK です。 これであれば何も定義しないのと同じでは?と思うかもしれませんが、この定義があることで、子要素を指定したときに確実に何らかの描画が行われるということを示すことができます。

type Props = {
  color: string
  children?: ReactNode
}

(おまけ) React.ReactElement や JSX.Element との違い

上記で children の型定義に使った React.ReactNode 以外にも、次のような似たような型があるのでまとめて起きます。

ReactElement / JSX.Element

React コンポーネントが生成する描画要素(<MyComponent> など)を表す型です。 関数コンポーネント (FC) の戻り値は、この ReactElement です。

(参考: DefinitelyTyped の定義

interface ReactElement<
  P = any,
  T extends string | JSXElementConstructor<any>
    = string | JSXElementConstructor<any>
> {
  type: T
  props: P
  key: Key | null
}

type Key = string | number;

React.FC インタフェースの定義を見ると、関数の戻り値として ReactElement が使われていることを確認できます。 TypeScript の型定義に慣れないと最初は理解しにくいかもしれませんが、下記の interface 定義の 1 つ目のプロパティが、関数コンポーネントのコールシグネチャを表しています。

type FC<P = {}> = FunctionComponent<P>

interface FunctionComponent<P = {}> {
  (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null
  propTypes?: WeakValidationMap<P>
  contextTypes?: ValidationMap<any>
  defaultProps?: Partial<P>
  displayName?: string
}

JSX.Element もほぼ同じもので、次のようなエイリアスなので、関数コンポーネントの戻り値の型として使用できます。 コンポーネントの実装において、部分的な要素を生成するユーティリティ関数の戻り値としても使用できます。

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

ReactChild

ReactChild は、ReactElement、文字列、数値のいずれかを表現する型です。 (参考: DefinitelyTyped の定義

type ReactChild = ReactElement | ReactText
type ReactText = string | number

一見これを children の型として使用できそうですが、ReactChild はあくまで 1 つの要素を表すものなので、複数の要素を指定できる children の型としては使用できません。

ReactNode

ReactNode は次のように定義されており、1 つ以上のコンポーネントやプリミティブ値を表します。 簡単にいうと、JSX コードの中の一部分はこの ReactNode で表現できます。 つまり、children の型としてはこの ReactNode を使うことができます。

type ReactNode =
  | ReactChild
  | ReactFragment
  | ReactPortal
  | boolean
  | null
  | undefined

type ReactFragment = {} | ReactNodeArray
interface ReactNodeArray extends Array<ReactNode> {}
interface ReactPortal extends ReactElement {
  key: Key | null
  children: ReactNode
}

まとめ

各タイプを粒度の観点で整理すると次のような感じです。

  • ReactNode (1 つ以上の React コンポーネント or 文字列 or 数値)
    • ReactChild (1 つの React コンポーネント or 文字列 or 数値)
      • ReactElement / JSX.Element (1 つの React コンポーネント)
      • ReactText (文字列 or 数値)

関連記事

まくろぐ
サイトマップまくへのメッセージ