@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.FunctionComponent
にprops: 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 Props
にchildren?: 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
のエイリアスでしかないので、定義内容は一緒です。似たような定義山ほどあった理由は、前方互換を担保したりショートハンドを提供するためだった、というお話でした。