tatsumiyamamoto.com

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

2022-12-15

# 最初に

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

# この記事を書く動機

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

# 前提知識

【検証】React.FC と React.VFC はべつに使わなくていい説
こんにちは、クレイの正岡です。 コロナ禍が始まってから小学生時代以来のゲーム生活を送っています。ゲームボーイと呼んでください。 さて、今回は React × Typescript でコードを書いている人/書こうとしている人に向けて、Reactコンポーネントの型定義について頭の片隅に置いておいて欲しい情報を共有したいと思います。 寝ながら使えてしまうReactコンポーネントの3つの型 () => JSX.Element 型 interface Props { text: string } const Hoge = ({ text }: Props) => { re
【検証】React.FC と React.VFC はべつに使わなくていい説 favicon https://kray.jp/blog/dont-have-to-use-react-fc-and-react-vfc/
【検証】React.FC と React.VFC はべつに使わなくていい説
github.com
github.com favicon https://github.com/typescript-cheatsheets/react#function-components

# 問題

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

x.com
x.com favicon https://twitter.com/oukayuka/status/1519158151202832384
x.com
x.com favicon https://twitter.com/oukayuka/status/1519158757183262722
x.com
x.com favicon https://twitter.com/uhyo_/status/1519159735916052480

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

## React v17 までの書き方

  • FC と VFC を使い分ける方法 基本は VFC を使い、children が props に必要なものだけ FC を使う:
React
// 普通のコンポーネントには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 に書く:

React
export const Layout: VFC<{ children: ReactNode }> = ({ children }) => {
  return (
    <>
      <Header />
      {children}
      <Footer />
    </>
  );
};
  • JSX.Element を使う方法(v18 でも変更なし)
JSX.Element
// 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>;
github.com
github.com favicon https://github.com/typescript-cheatsheets/react#function-components

## React v18 による変更

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

Removal of implicit children
Explainer why we intend to get rid of implicit children in `@types/react`
Removal of implicit children favicon https://solverfox.dev/writing/no-implicit-children/

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

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

React
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 を素早くタイプできるのはいいのですが、さまざまなバグも隠されてしまいます。

Removal of implicit children
Explainer why we intend to get rid of implicit children in `@types/react`
Removal of implicit children favicon https://solverfox.dev/writing/no-implicit-children/

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

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

# これからどう書くか

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

## FC を使う

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

FCを使う方法
// 普通のコンポーネントの書き方
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 箇所書く必要がある
JSX.Elementを使う方法
// 普通のコンポーネントの書き方
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 />
    </>
  );
};