Skip to content

ReactElementとReactNodeの違い

JSX.IntrinsicElements

まず、本題に入る前にJSX.IntrinsicElementsについて抑えておく。

しかしこれについて理解するには、ReactではなくTypeScriptのドキュメントを読む必要がある。
要はJSXやTypeScriptがJSXをどう扱うかを知らないといけない。

まず、JSX式<expr />で作られる要素には2つの場合が存在する。

  • intrinsic elements
  • value-based elements

intrinsic elements

これは<html><div>などの組み込み要素のこと…と言い切ってしまうのは少々乱暴。
厳密に言うなら、「TypeScriptが組み込み要素として認識する要素」のことを指していると考えられる。

なぜなら、HTMLの仕様書で定義されていないような非組み込み要素であっても、以下のようにして組み込み要素であるかのようにTypeScriptに認識させることができるからだ。

declare namespace JSX {
interface IntrinsicElements {
foo: {
hoge: string;
fuga: number;
}
}
}
const elm = <foo hoge="文字列" fuga={123} />; // OK
const elm2 = <foo hoge={123} fuga={456} />; // エラー: hogeはstring型でなければならない
const elm3 = <bar />; // エラー: barという要素は定義されていない

この例では、TypeScriptはfooという要素が組み込み要素であると認識している。

視点を変えれば、TypeScriptはJSX.IntrinsicElementsという型を調べて、<expr />が組み込み要素であるかを判断していると考えられる。

という前提で@types/reactに書かれているJSX.IntrinsicElementsの型定義を見てみると「なるほど!」となる。

value-based elements

こちらに関しては、先述のintrinsic elementsの説明における表現を借りれば、「ユーザーが定義した要素であるとTypeScriptが認識した要素」を指す。

React.ReactNodeReact.ReactElement

この前提をおさえて本題に入る。

React.ReactNodeReact.ReactElementはそれぞれどういう概念なのだろう?

Reactのドキュメントによると、まずReact.ReactNodeは以下のように定義されている。

  • や createElement(‘div’) のようにして作成された React 要素
  • createPortal で作成されたポータル
  • 文字列
  • 数値
  • true, false, null, undefined(これらは表示されません)
  • 他の React ノードの配列

定義が分かったところでReact.ReactNode型は何を表現する集合なのだろう?
その答えは型定義に書いていた。

types/react/index.d.ts
/**
* Represents a JSX element.
*
* Where {@link ReactNode} represents everything that can be rendered, `ReactElement`
* only represents JSX.
*
* @template P The type of the props object
* @template T The type of the component or tag
*
* @example
*
* ```tsx
* const element: ReactElement = <div />;
* ```
*/
interface ReactElement<
P = any,
T extends string | JSXElementConstructor<any> =
| string
| JSXElementConstructor<any>
> {
type: T
props: P
key: string | null
}
// ...
/**
* Represents all of the things React can render.
*
* Where {@link ReactElement} only represents JSX, `ReactNode` represents everything that can be rendered.
*
* @see {@link https://react-typescript-cheatsheet.netlify.app/docs/react-types/reactnode/ React TypeScript Cheatsheet}
* ...
*/
// non-thenables need to be kept in sync with AwaitedReactNode
type ReactNode =
| ReactElement
| string
| number
| Iterable<ReactNode>
| ReactPortal
| boolean
| null
| undefined
| DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_REACT_NODES[keyof DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_REACT_NODES]

これらの型定義によると以下のことがわかる。

  • React.ReactElementはJSXで表現される要素の集合である。
  • React.ReactNodeはReactがレンダーできる物の集合である。
  • React.ReactElementReact.ReactNodeの部分集合である。

React.ReactNodeについてはある程度理解できた。
一方でReact.ReactElementはまだしっくり来てない。

React.ReactElementって一体なんなのか?それを理解するためには、どうやら同時にJSX.Elementについても理解する必要があるらしい。

まず、JSX.Elementの型定義を見る。

types/react/index.d.ts
declare global {
/**
* @deprecated Use `React.JSX` instead of the global `JSX` namespace.
*/
namespace JSX {
// ...
interface Element extends React.ReactElement<any, any> {}
// ...
}
}

つまり、JSX.ElementReact.ReactElementのサブタイプであることがわかる。

ここで、冒頭のTypeScriptのドキュメントのJSXのページに戻るとこんな事が書いてある。

Function Component As the name suggests, the component is defined as a JavaScript function where its first argument is a props object. TS enforces that its return type must be assignable to JSX.Element.

つまり、JSX.Elementという型は関数コンポーネントの戻り値を表現する型であることが分かる。

また、React.createElement()の型定義も見てみる。返り値がReact.ReactElement型であることがわかる。

types/react/index.d.ts
function createElement<P extends {}>(
type: FunctionComponent<P> | ComponentClass<P> | string,
props?: (Attributes & P) | null,
...children: ReactNode[]
): ReactElement<P>

うーん…今度はJSX.Elementについては分かったけど、React.ReactElementについてはまだしっくり来てない。

参考