😡

一番文句言われなさそうな React コンポーネントの書き方

2021/04/15に公開
2

最近 React コード生成機を作っていて「一番文句言われなさそうな React コンポーネントの書き方ってなんだ…?」と改めて疑問に思ったので考えてみました。

結論から言うと以下の形をデフォルトにするのが良さそうかなと思いました。

  • function vs. アロー関数 -> アロー関数
  • 型は基本的に VFC でつけて、 children が欲しい場合は明示的に props に追加する
  • return を省略可能な時省略するか -> しない
  • props を destructure するか -> しない派だったけどした方がいい気がしてきた
const Hoge: React.VFC<Props> = ({ title }) => {
  return (
    <Fuga title={title} />
  )
}

ちなみにですが、大事な前提として TypeScript を使うことを前提としています。(型が判断に関わってくるので)
あと基本的に判断軸としては「あとからの変更が少ないこと」です。見た目はそれっぽくても言語化できる利点が思い当たらないものよりは、後からいじる時の変更量が減るものを選ぼうと思ってます。

以下思考の過程です。

function vs アロー関数

function Hoge(props) {

}

// vs

const Hoge = (props) => {

}

後者が多数なんじゃないかと思っていますが、実は私は長らく(というか今も手癖で)前者の function で定義してました。

というのも Dan 先生の下記 Tweet にインスパイアされたからです。

意訳すると

const の return を省略する記法を繰り返し定義し直すくらいなら最初から function で書いちゃえばいいんじゃない。言語の記法を無理に全部使おうとしなくていいんだよ。ハハッ

という感じでしょうか。

私はこれを受けて「確かに const 使っているせいで省略するかどうかという選択が生まれているから function で統一してしまえば…。何よりそっちで書く方が分かってる感出るし、理由を聞かれても「Dan 先生がそう言ってた!」で乗り切れるしな!」と思い function 記法を採用しました。

困ったこと: children の型が手に入らぬ

採用していたのですが、困ったこととして React.FC で定義しないと children が Props の型に含まれません。なので children が必要な時だけ const で書くようにしていたのですが、段々「記法がブレててよくないのでは」と感じるようになりました。
(一応補足しておくと { children?: React.ReactNode } を Props の型に入れたら定義はできる)

こういう型的な事情があって、基本的には

const Comp: React.FC<Props> = (props) で統一するのが変更量少なくていいのではないかと思うようになりました。

と思ったけど将来的に FC からも children なくなるってよ

remove implicitly typed children from React.FC

https://github.com/DefinitelyTyped/DefinitelyTyped/issues/46691

だそうです。「暗黙的に children の型渡っちゃうの、children が反映されるコンポーネントなのか型だけ見て分からないから良くないよね」ということみたいですね。

現状は React.VFC を使って children は Props の型定義を書き、将来的に React.FC からも children がいなくなったら置換することが推奨されています。それだとさっきの function の書き方と変わらない気もしますが、せっかく推奨されているので React.VFC を使うことにします。

番外編 React.VFC つけるメリットって何?

それだとさっきの function の書き方と変わらない気もしますが、せっかく推奨されているので React.VFC を使うことにします。

ここなんですが children 目当てで React.FC をつけていた身としては 「children いなくなるならこの型つける意味って何?」と思ったので調べてみました。

一目で React コンポーネント名と分かる

まあ function でも引数の名前が Props で、 return しているのが JSX だったら瞬時に「あ、Reactコンポーネントだ」と認知できるのでそんなに困りませんが、明示的に型をつける一個のメリットであると言えるでしょう。

defaultProps や displayName の型を定義してくれる

具体的な @types/react の型を見てみると次のプロパティがあるみたいです。
これらを活用する時もあるかもしれないのでデフォルトでつけておくという選択はいいのかなと思います。

interface VoidFunctionComponent<P = {}> {
	(props: P, context?: any): ReactElement<any, any> | null;
	propTypes?: WeakValidationMap<P>;
	contextTypes?: ValidationMap<any>;
	defaultProps?: Partial<P>;
	displayName?: string;
}

https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/v16/index.d.ts#L552

const の return の部分を可能な時に省略するか

const Hoge = () => <Fuga />

vs

const Hoge = () => {
  return (
    <Fuga />
  )
}

個人的には常に後者のように全て書くのがいいのではないかと思います。理由としては、先程の Dan 先生があげていたものと被りますが、前者で書いた時に State や関数を足したくなった時に書き換えなければいけない部分が増えるからです。それなら最初から全部書いておけば?というのが個人的な感想です。

props を destructure するかどうか

const Hoge:React.VFC<Props> = (props) => {
  return (
    <Fuga title={props.title} />
  )
}

// vs

const Hoge:React.VFC<Props> = ({ title }) => {
  return (
    <Fuga title={title} />
  )
}

個人的には destructure しない方がいいのではないかと考えています。
理由としては {...props} のようにスプレッドで渡したくなる時が稀によくあるのと、props の型が変わる時にいじる箇所が増えるからです(型の部分に加えて destructure しているところをいじる必要がある)。
props. まで打ったらサジェストが出るのでタイピング速度も大して変わらないかなと思います。

追記

と思っていたのですが Takepepeさんのツイート を読み、className のようなそのまま子に渡すと困るプロパティがあったりするので、そういったものを一部取り除く時に次のように destructure を使うのは確かに便利だと気づきました。

const Hoge = ({ className, ... props }) => {}

また、透過的に渡すにしても、上記のようなものを知らない内に渡してしまう事故を防ぐためにデフォルトで destructure する方がいいのではないかという気がしてきました。

また、f_subalさんのツイート

props を destructuring しないと未使用 props が放置される

というのも、使わなくなった props があった時に props オブジェクトのまま使っていたら自動で検出できないので確かにそうだなと思いました。
さらに destructure した方が良さそうだなという気持ちが高まりました。

結論再掲

こちらでいかがでしょうか!

  • function vs. アロー関数 -> アロー関数
  • 型は基本的に VFC でつけて、 children が欲しい場合は明示的に props に追加する
  • return を省略するか -> しない
  • props を destructure するか -> しない派だったけどした方がいい気がしてきた
const Hoge: React.VFC<Props> = ({ title }) => {
  return (
    <Fuga title={title} />
  )
}

Discussion

iceice

困ったこと: children の型が手に入らぬ

こちらですが、 PropsWithChildren という型を使うと children の型も簡単に使えます!
参考までにどうぞ:

import React, { PropsWithChildren } from 'react'

interface MyProps { name: string }

function MyComponnent({ name, children }: PropsWithChildren<MyProps>) {
  return <div>name: {name}<div>{children}</div></div>
}

(VFC の存在自体を知らなかったので、いい情報ありがとうございました!
※2023-09-05 今頃ですが MyComponent の props が { name } となっていて children が入っていなかったので修正しました

seyaseya

そんな便利型があったんですね!!
情報ありがとうございます🙏