🐷

VFC, FC JSX.Element、どれを使うべき?初心者でも分かるように解説!

2024/09/03に公開

こんにちは、ヒロケイです。

今回は、React におけるコンポーネントの型定義についての話をしようと思います。

  • JSX.Element, FC, VFC どれを使えば良いの?

  • それぞれどんな特徴があるのだろうか。

PR の参考や、モヤモヤの解消に役立てれば幸いです(^^)

結論

とりあえず

JSX.Element、または React.FC を使おう。

React.VFC は使ってはいけない!

このようになっています。

なぜに React.VFC は使っていけないの?

バージョンの事情で React.VFC は非推奨になっているんだ。

それぞれの型の特徴を見ていこう!

FC と VFC の違い

React で用意されている型定義の違いについて見ていきましょう。

(読みやすさのため、省略してあります。)

FC の型定義(node_module 下にあるファイル)

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

interface FunctionComponent<P = {}> {
    (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
}

VFC の型定義(node_module 下にあるファイル)

type VFC<P = {}> = VoidFunctionComponent<P>;

interface VoidFunctionComponent<P = {}> {
    (props: P, context?: any): ReactElement<any, any> | null;
//          ↑ここがPropsWithChildren<P>からPになっているだけ

Props にコンポーネント型を設定するかで違いが出る

上記二つの型定義の違いは、props に渡す型がちょびっと違う程度でしたね。

では、その違いについて掘り下げてみましょう。

二つの型で違いが出るのは、Props にコンポーネントを指定するときです。

React.FC で指定した場合 ↓

export const Layout: React.FC = ({ children }) => {
  return <div>{children}</div>;
};

React.VFC で指定した場合 ↓

export const Layout: React.VFC<{ children: React.ReactNode }> = ({ children }) => {
  return <div>{children}</div>;
};

どちらも定義しているコンポーネントの機能は変わりませんが、React.VFC の場合はジェネリクスに Props の型を明示的に指定する必要があります。

一方、React.FC は Props に children の型を指定しなくても Props を設定できます。

明示的に指定しなくても children を指定できることから、暗黙の children と言われています

React.VFC は使うべきではない

元々は React では FC しか無かったのですが、暗黙の children 型を許してしまうと、必要の無い型が含まれてしまうという指摘から React.VFC が生まれました。(VFC が登場した PR)

しかし、React のバージョンが 18 になってから型定義が大きく変わってきます。

React.FC が暗黙の children を許さなくなったのです。

つまり、React.FC が React.VFC と同じ型定義になったわけです。

そうすると、React.VFC はもう必要なくなります。

同じ型を二つ用意している必要はないからね!

その結果、v18 では、React.VFC を指定すると notation が表示されます。

プロジェクトの package.json で dependencies に格納されている React のバージョンを確認してみると良いですね(^^)

JSX.Element と FC の違い

次に、React.FC と JSX.Element の違いについてみていきましょう!

JSX.Element はジェネリックコンポーネントが使える

JSX.Element は、様々な型に対応するジェネリックコンポーネントに対応しています。

interface Props<T> {
  items: T[];
  renderItem: (item: T) => ReactNode;
}

const List = <T,>(props: Props<T>): JSX.Element => {
  const { items, renderItem } = props;
  return <div>{items.map(renderItem)}</div>;
};

引数の型にジェネリック型を用意することで、Props に string や Object を代入しても機能する汎用的なコンポーネントを作ることができます。

function App() {
  return (
    <>
      <List
        items={['hirokei', 'deno', 'bot']}
        renderItem={(item) => <p>{item}</p>}
      />
      <List
        items={[
          { name: 'mickey', position: 'leader' },
          { name: 'minnie', position: 'designer' },
          { name: 'walt', position: 'ceo' },
        ]}
        renderItem={(user) => (
          <>
            <h1>{user.name}</h1>
            <p>{user.position}</p>
          </>
        )}
      />
    </>
  );
}

これは、React.FC ではできない使い方ですね(^^)

何故なら、コールバック関数外で型指定をするために具体的な型を指定する必要があるからです。

型指定方法が違う

type Props = {
  title: string;
  description: string;
};

export const Card: React.FC<Props> = ({ title, description }) => {
  return (
    <div>
      <h2>{title}</h2>
      <p>{description}</p>
    </div>
  );
};
type Props = {
  title: string;
  description: string;
};

export const Card = ({ title, description }: Props): JSX.Element => {
  return (
    <div>
      <h2>{title}</h2>
      <p>{description}</p>
    </div>
  );
};

JSX.Element では、コールバック関数内で型を指定する必要があります。

Props の型指定方法に関して、React.FC では型指定をした際にジェネリクスの中に Props の型を指定できます。

一方、JSX.Element は引数内に直接指定する必要があります。

型定義にはどんな違いがあるの?

React.FC と JSX.Element では、型定義にどんな違いがあるのかをみていきましょう!

namespace JSX {
        interface Element extends React.ReactElement<any, any> { }
type FC<P = {}> = FunctionComponent<P>;

interface FunctionComponent<P = {}> {
    (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
}

主な違い

  • React.FC: コールバック関数が返した結果が React.ReactElement

  • JSX.Element: ReactElement を拡張した空オブジェクト

型定義上ではそんなに大きな違いがないことがわかりますね!

まとめ

まとめとして、

  • JSX.Element か React.FC を使おう!

  • どちらを使うかは既存コンポーネントの型指定に従って OK

  • VFC はこれから非推奨だから NG

このような結果になりました。

最後まで読んでいただき、ありがとうございました。

Discussion