🔥

コンポーネントに型をつけるときFCを使う?それともJSX.Elementを使う?

2022/12/15に公開約3,600字

最初に

この記事はqiitaから移行したものです。

この記事を書く動機

メモ
間違っている部分があればコメントで教えてください。

前提知識

https://kray.jp/blog/dont-have-to-use-react-fc-and-react-vfc/

https://github.com/typescript-cheatsheets/react#function-components

問題

以下、尊敬するエンジニアの方々のツイートを引用させていただきました(いつもツイートから勉強させてもらっています。ありがとうございます!):

https://twitter.com/oukayuka/status/1519158151202832384

https://twitter.com/oukayuka/status/1519158757183262722

https://twitter.com/uhyo_/status/1519159735916052480

これまでの経緯を振り返る

React v17までの書き方

  • FCとVFCを使い分ける方法
    基本はVFCを使い、childrenがpropsに必要なものだけFCを使う:
// 普通のコンポーネントにはVFCを使う
export const Button: VFC<{ text: string }> = ({text}) => {
  return (
    <button>{text}</button>
  );
}

// ラッパーコンポーネントなどchildrenが必要なものだけFCを使う:
export const Layout: FC = ({children}) => {
  return (
     <>
       <Header />
       {children}
       <Footer />
     </>
  );
}

もしくはVFCだけを使い、childrenが必要なときは明示的にpropsに書く:

export const Layout: VFC<{ children: ReactNode }> = ({children}) => {
  return (
     <>
       <Header />
       {children}
       <Footer />
     </>
  );
}

  • JSX.Elementを使う方法(v18でも変更なし)
// propsの型の宣言
type AppProps = {
  message: string;
};

// Function Componentの最も簡単な宣言方法です。返り値の型は推論されます。
const App = ({ message }: AppProps) => <div>{message}</div>;

// もし返り値として違う方を返したときにエラーになるように、返り値の型を注釈することもできます
const App = ({ message }: AppProps): JSX.Element => <div>{message}</div>;

// インラインで型宣言をすることもできます。propsの型の命名を省くことができますが、くどく見えます
const App = ({ message }: { message: string }) => <div>{message}</div>;

https://github.com/typescript-cheatsheets/react#function-components

React v18による変更

React 18 では、@types/react で、私たちが長い間抱えていた問題を修正する機会を得ました。私たちはもともとReact 17でこれらを修正したかったのですが、React 17が段階的な移行を可能にする大きなステップだったため、保留にしていました。これらの変更の1つは、React.FunctionComponent型における暗黙のchildrenの削除です。なぜこの変更を行いたいのか、どのようにすれば移行を楽にできるのかを説明していこうと思います。

https://solverfox.dev/writing/no-implicit-children/

FCに暗黙のchildrenが渡されなくなった

React v18以降では、以下のコードが動かなくなる:

import * as React from 'react';

const Input: React.FC = ({ children }) => <div>{children}</div>;
//                         ^^^^^^^^ will error with "Property 'children'
//                                  does not exist on type '{}'.

一言でいうと、v18以降のFCはそれまでのVFCになった。

VFCが非推奨になった

なった。

Q. なんでこんなことしたの?

A. 暗黙のchildrenは、確かにcomponentを素早くタイプできるのはいいのですが、さまざまなバグも隠されてしまいます。

https://solverfox.dev/writing/no-implicit-children/

React.FCから暗黙のchildrenを削除することは、具体的には次の点で効果があるとのこと:

  • React.FCと単純なfunction宣言との間で、挙動の一貫性がとれる
  • 余剰なchildrenがpropsとして与えられたときにエラーになる

これからどう書くか

大きく2つに分けられると思っています。(他あったら教えてください)
これに関してはチームで統一する必要があると思っています。

FCを使う

ありです。
あえてデメリットを挙げるなら、「React FC」とかで検索するとv17までの記事が結構でてきて、「FCよりVFCを使うべき!」みたいなこと書かれてるのがちょっと怖いかなって思っています。

// 普通のコンポーネントの書き方
export const Button: FC<{ text: string }> = ({text}) => {
  return (
    <button>{text}</button>
  );
};

// childrenが必要なコンポーネントの書き方(PropsWithChildrenを使う書き方もあるようです)
export const Layout: FC<{ children: ReactNode }> = ({children}) => {
  return (
     <>
       <Header />
       {children}
       <Footer /> 
     </>
  );
};

JSX.Elementを使う

ありです。ちなみに僕はこっち派です。
理由はこれまでそうしてきて、v18でも特に書き方は変わらないみたいなので...。
デメリットとしては冒頭にうひょさんのツイートにあるように次の点にあるみたいです:

  • 関数の返り値の型を推論させる必要がある
  • 引数と返り値の型を2箇所書く必要がある
// 普通のコンポーネントの書き方
export const Button = ({text}: { text: sring }): JSX.Element => {
  return (
    <button>{text}</button>
  );
};

// childrenが必要なコンポーネントの書き方
export const Layout = ({children}: { children: ReactNode }): JSX.Element => {
  return (
     <>
       <Header />
       {children}
       <Footer /> 
     </>
  );
};

Discussion

ログインするとコメントできます