203
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

updated at

Organization

雰囲気で使わない @types/react

@types/react の型定義を雰囲気で使っている皆さん、こんにちは。React コンポーネントを書くときはどの型を使い、どう書くべきか?という疑問を Twitter でお見かけし、雰囲気で使っていた私も気になったので @types/react の中身を覗いてみました。

master に入り、いよいよリリース間近な Hooks。本稿は FC(旧SFC) の話が前提です。VScode で型付けをしようとすると、似たような型が山ほど出てきて、どれを使うのが適切なのか・違いが何なのか…パッと見分かりませんね。

  • ReactNode
  • ReactChild
  • ReactElement
  • StatelessComponent
  • FunctionComponent
  • JSX.Element
  • SFC
  • FC

という訳で、こんな Props を持っているコンポーネントの各種書き方を比較、白黒ハッキリさせていきたいと思います。

type Props = { test: 'test' }

🏆 Winners

結論からいうと、以下二つのどちらかの書き方が良さそうです。

const MyComponentA: React.FunctionComponent<Props> = props => (<></>)
const MyComponentB: React.FC<Props> = props => (<></>)

引数の props に型注釈を入れていませんが、これだけで推論はちゃんと動いてくれます。定義元の React.FunctionComponentprops: P & { children?: ReactNode }が既に入っているので、プログラマー定義のP型 type Props = { test: 'test' } とマージしてくれます。これが勝者たる所以です。React.FC は React.FunctionComponent の定義を参照しているため、全く同じものと言えます。

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

React.FunctionComponent が本流ですが、長ったらしいので、React.FC のほうが自分は好みです。つぎに、残念ながら良くなさそうな書き方を紹介します。

🙅‍♀️ children? を自前で入れる必要アリ

基本的に推論に頼りたい派なので、今まで自分はこの書き方をしていました。render children が必要な度、type Propschildren?: ReactNode を自前で注入しなければならず、やや冗長です。

const MyComponentC = (props: Props) => (<></>)
const MyComponentD = (props: Props): JSX.Element => (<></>)
const MyComponentE = (props: Props): React.ReactElement<any> => (<></>)

MyComponentCの戻り型推論はJSX.Elementで、JSX は @types/react の中で切られている namespace です。JSX.Element は、React.ReactElement を空継承しています。つまり、この3つの書き方は全く同じであり、後者二つは戻り型注釈付与の意味がない事になります。

interface Element extends React.ReactElement<any> { }

🙅‍♂️ 型が緩い

特に困ることはないですが、React.ReactElement を含んだ UnionTypes なので次の書き方でもコンパイルは通ります。ReactNode や ReactChild には、FunctionComponent 以外にも色んな型が UnionTypes で入っており、コンポーネント以外の型へも、制限を緩めてしまっています。ReactText の実体は string | number です。

// type ReactChild = ReactElement<any> | ReactText;
const MyComponentF = (props: Props): React.ReactChild => (<></>)

// type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;
const MyComponentG = (props: Props): React.ReactNode => (<></>)

こんなコンポーネントを作る人はいないでしょうが、コンパイルエラーを得られていないことから、間違っていることは明らかです。

const MyComponentF = (props: Props): React.ReactChild => (0)
const MyComponentG = (props: Props): React.ReactNode => (null)

😭 そもそも deprecated

@deprecated as of recent React versions, function components can no longer be considered 'stateless'. Please use FunctionComponent instead. @see React Hooks

だそうです。Hooks の影響をもろに受けている…。この書き方をしていたら、気分が向いた時に書き換えましょう。

const MyComponentH: React.StatelessComponent<Props> = props => (<></>)
const MyComponentI: React.SFC<Props> = props => (<></>)

ちなみに、この二つどちらもReact.FCと同様に、React.FunctionComponentのエイリアスでしかないので、定義内容は一緒です。似たような定義山ほどあった理由は、前方互換を担保したりショートハンドを提供するためだった、というお話でした。

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
203
Help us understand the problem. What are the problem?