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) を表示します。
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 数値)