コンポーネントの型としてReact.FC(React.FunctionComponent)を使わないほうがいい理由

ReactTypeScript
2021-10-14

React TypeScript Cheatsheet を見ていたら、React.FCを非推奨してたので、理由を少し調べてみた。

主な理由はこのCRAのprに書いてあった。

https://github.com/facebook/create-react-app/pull/8177

マイナス面として

  • 暗黙的な children の定義してしまう
    • React.FCでコンポーネントを定義すると、暗黙的にchildren(ReactNode型)を取るようになり、想定していなくても、すべてのコンポーネントがchildrenを受け入れることになり、以下のようなコードが存在できる。 ランタイムエラーとはならないが、本来はミスであり、TSで判定できない。
const App: React.FC = () => { /*... */ }; 
const Example = () => {
  // <App/> はchildrenを利用しないが、渡せてしまう。
	<App><div>Unwanted children</div></App>
}
  • genericsをサポートしていない
    • (通常であれば)以下のようなコンポーネントを定義することができる
type GenericComponentProps<T> = {
   prop: T
   callback: (t: T) => void
}
const GenericComponent = <T>(props: GenericComponentProps<T>) => {/*...*/}

しかし、React.FCを使用する場合genericの Tを保持、返却する仕組みがないため、これらが不可能です。

const GenericComponent: React.FC</* ??? */> = 
  <T>(props: GenericComponentProps<T>) => {/*...*/}
  • 「名前空間としてのコンポーネントパターン」をより厄介なものにしてしまう
    • コンポーネントを、関連するコンポーネントの名前空間として使用する(通常は children)のは、やや一般的なパターンです。
<Select>
	<Select.Item />
</Select>

これはReact.FCでも可能ですが、以下のように厄介なことになります。

// 依存するコンポーネントの型を定義しないといけない
const Select: React.FC<SelectProps> & { Item: React.FC<ItemProps> } =
  (props) => {/* ... */ }

Select.Item = (props) => { /*...*/ }

React.FCがないと問題なく動作します

const Select = (props: SelectProps) => {/* ... */}
Select.Item = (props: ItemProps) => {/* ...*/ }。
  • defaultPropsでは正しく動作しない
💡
defaultPropsは非推奨となるのでこの件は気にしなくてもよい(デフォルト値の実装ははESのを利用する。)
  • どちらの場合もES6のデフォルトの引数を使用する方がおそらく良いので、これはかなり議論の余地のあるポイントですが...
type  ComponentProps = { name: string; }

const  Component = ({ name }: ComponentProps) => (<div>
	{name.toUpperCase()} /* Safe since name is required */
</div>);
Component.defaultProps = { name: "John" };

const  Example = () => (<Component />) /* Safe to omit since name has a default value */

これは正しくコンパイルされる。

React.FCを利用した場合は、少しだけ間違ったものになる

  • React.FC<{name: string}> を利用した場合、nameはオプションではなく必須となり
  • React.FC<{name?: string}> とした場合はtoUpperCase()でエラーになってしまう

なので「内部的には必須、外部的にはオプション」という動作を再現する方法はないです。

defaultPropsは非推奨となるのでそもそも気にしなくてもよい(デフォルト値はESのを利用する。)

  • コードが長くなる(FunctionalComponentを使っている場合は特に)
    • 大きなポイントではないが、 React.FCを利用しているよりは短く書ける
const C1: React.FC<CProps> = (props) => {};
const C2 = (props: CProps) => {};

プラス面

  • 明示的な戻り値の型を提供する
    • React.FC の場合は戻り値を明示的に定義できる点であり、以下のようにundefindを返す場合は、コンパイル時にエラーとならない。
const Component = () => {
   // コンポーネントはundefinedを返すことはできない(`null`のみ)
   return undefined; 
}

ただし、この場合、実際には実行時にエラーとして補足されるので、あまり形式的には変わらないかも?

// コンポーネントの戻り値が正しくないのでエラーになる
const Example = () => <Component />;

まとめ

  • React.VFC, React.FCを利用しない場合は明示的に JSX.Element を戻り値の型としてしたほうがよい(個人的には)
const App = ({ message }: AppProps): JSX.Element => <div>{message}</div>;

avatar
Written by Kyohei Tsukuda who lives and works in Tokyo 🇯🇵 , building useful things 🔧.