Closed4

【メモ】ReactElement?ReactNode?React.FC?JSX.Element??

はっしーはっしー

Reactの1要素を表す型ってめちゃめちゃある
それぞれの違いが気になったので軽く調べてまとめてみる

知りたいこと

  • ReactElement、ReactNode、React.FC、JSX.Elementの違いって何?
  • みんな使い分けたりしてるの?
はっしーはっしー

記事を読みつつ自分なりにまとめてみる

ReactNode, ReactElementの違い

ReactElementのコード
    /**
     * 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 = unknown,
        T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>,
    > {
        type: T;
        props: P;
        key: string | null;
    }
    
ReactNodeのコード
    /**
     * Different release channels declare additional types of ReactNode this particular release channel accepts.
     * App or library types should never augment this interface.
     */
    interface DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_REACT_NODES {}

    /**
     * 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}
     *
     * @example
     *
     * ```tsx
     * // Typing children
     * type Props = { children: ReactNode }
     *
     * const Component = ({ children }: Props) => <div>{children}</div>
     *
     * <Component>hello</Component>
     * ```
     *
     * @example
     *
     * ```tsx
     * // Typing a custom element
     * type Props = { customElement: ReactNode }
     *
     * const Component = ({ customElement }: Props) => <div>{customElement}</div>
     *
     * <Component customElement={<div>hello</div>} />
     * ```
     */
    // non-thenables need to be kept in sync with AwaitedReactNode
    type ReactNode =
        | ReactElement
        | string
        | number
        | bigint
        | 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
        ]
        | Promise<AwaitedReactNode>;
     * Where {@link ReactNode} represents everything that can be rendered, `ReactElement`
     * only represents JSX.

ReactNode は「描画可能なものすべて」(文字列、数値、ReactElementなど)を表すのに対し、
ReactElement は「JSXとして記述された要素」だけを表す。らしい

つまりこういうこと。ReactNodeのほうがより広い型になっている。

const a = "Hello"; // 文字列
const b = 123; // 数値
const c = null; // null
const d = <div>Hi</div>; // JSX(ReactElement)
const e = ["a", 1, null]; // 配列

const node_a: ReactNode = a; // ✅ OK
const node_b: ReactNode = b; // ✅ OK
const node_c: ReactNode = c; // ✅ OK
const node_d: ReactNode = d; // ✅ OK
const node_e: ReactNode = e; // ✅ OK

const element_a: ReactElement = a; // ❌️ 型 'string' を型 'ReactElement<unknown, string | JSXElementConstructor<any>>' に割り当てることはできません。
const element_b: ReactElement = b; // ❌️ 型 'number' を型 'ReactElement<unknown, string | JSXElementConstructor<any>>' に割り当てることはできません。
const element_c: ReactElement = c; // ❌️ 型 'null' を型 'ReactElement<unknown, string | JSXElementConstructor<any>>' に割り当てることはできません。
const element_d: ReactElement = d; // ✅ OK
const element_e: ReactElement = e; // ❌️ 型 '(string | number | null)[]' には 型 'ReactElement<unknown, string | JSXElementConstructor<any>>' からの次のプロパティがありません: type, props, key

React.FC

コード
    /**
     * Represents the type of a function component. Can optionally
     * receive a type argument that represents the props the component
     * receives.
     *
     * @template P The props the component accepts.
     * @see {@link https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/function_components React TypeScript Cheatsheet}
     * @alias for {@link FunctionComponent}
     *
     * @example
     *
     * ```tsx
     * // With props:
     * type Props = { name: string }
     *
     * const MyComponent: FC<Props> = (props) => {
     *  return <div>{props.name}</div>
     * }
     * ```
     *
     * @example
     *
     * ```tsx
     * // Without props:
     * const MyComponentWithoutProps: FC = () => {
     *   return <div>MyComponentWithoutProps</div>
     * }
     * ```
     */
    type FC<P = {}> = FunctionComponent<P>;

     * Represents the type of a function component. Can optionally
     * receive a type argument that represents the props the component
     * receives.

関数コンポーネントの型を表します。オプションで、そのコンポーネントが受け取る props の型を型引数として指定することができます。

ということなので関数型コンポーネントを表すのがReact.FC

const MyComponent: React.FC<{ title: string }> = ({ title }) => {
  return <h1>{title}</h1>;
};

JSX.Element

そもそもJSXとはなにかというと

JSX(JavaScript XML)は、コンポーネント指向のJavaScriptライブラリやフレームワーク(特にReact)で一般的に採用されている、JavaScriptの拡張構文です。JSXを用いると、JavaScriptのコード内にHTMLタグのような構文が埋め込み可能となり、より直感的かつ読みやすい形でUIのコードを表現することができます。

とのこと

https://typescriptbook.jp/reference/jsx

ただコードをみると その実態はReactElementになっているみたい?

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

余談だがReact19だとReact.JSX.Elementで参照するようになっていた、もともとJSX.Elementだったはずだけど ...

https://ja.react.dev/blog/2024/04/25/react-19-upgrade-guide#the-jsx-namespace-in-typescript

ってことは先程調べた結果がそのまま使えそう
つまりReactElementから型引数をなくしたものがJSX.Elementということか

はっしーはっしー

まとめ

  • ReactNode は「描画可能なものすべて」(文字列、数値、ReactElementなど)
  • ReactElement は「JSXとして記述された要素」
  • React.FCは関数型コンポーネント
  • JSX.ElementはReactElementの2つの型引数をanyで代入したもの
  • 状況に応じて使い分けることはできそう
このスクラップは6ヶ月前にクローズされました