関数コンポーネントの自由度が高すきた件
関数コンポーネントの書き方は、JavaScript 自体がそうであるように、10通り以上と、かなり自由度が高くなっています。自分は React 初心者なので、この書き方に毎回迷う部分がありました。そのような些細な部分で悩まなくて済むよう、ある程度自分の中で方針を定めたいなと思い、色々と調べたので、その結果をまとめておきたいと思います。
個人的な結論と完成品
予め用意しておいた完成品がこちらになります。
- props の分割代入を行う
- 型エイリアスを書く
- 型エイリアスはコンポーネント定義の前に書く
- React.VFC は使わない
- default export をする
- const とアロー関数ではなく function で書く
- export default を function と同じ行に書く
interface Props {
foo: Foo;
bar: Bar;
}
export default function AwesomeComponent({ foo, bar }: Props): JSX.Element {
return (
<>
Hello World
</>
);
}
named export × アロー関数バージョン
別のパターンとして、named export
とアロー関数を使用したものも用意しておきます。
- named export をする
- const とアロー関数で書く
- export を const と同じ行に書く
interface Props {
foo: Foo;
bar: Bar;
}
export const AwesomeComponent = ({ foo, bar }: Props): JSX.Element => {
return (
<>
Hello World
</>
);
}
default export
か named export
はプロジェクトごとにどちらかを採用するのが良いでしょう。
指針の具体的な説明
props の分割代入を行う
- 理由:その方が全体の記述量を抑えられるため
-function AwesomeComponent(props: Props): JSX.Element {
+function AwesomeComponent({ foo, bar }: Props): JSX.Element {
props はオブジェクトの形で渡されますが、大抵の場合は中身それぞれを個別に利用するので、1つの変数にまとめておく利点はあまりありません。逆に props をそのまま子コンポーネントに渡すような場合は、props のまま受け取ってしまった方が良いでしょう。加えて、props という単語にも特別な意味はないので、コードの読みやすさが上がることも恐らく無いでしょう。また、パラメータとローカル変数の違いを意識しなくて済み、自動補完の恩恵も受けやすくなります。もちろん、分割代入により記述量も減らすことができます。
- return props.foo.x * props.foo.y + props.bar.x * props.bar.y * props.bar.z;
+ return foo.x * foo.y + bar.x * bar.y * bar.z;
自分がかつてそうであったように、もしあなたが JavaScript や React の初心者で、分割代入が難しいと思うのであれば、まずは分割代入なしの書き方で理解を深める方が良いかもしれません。
型エイリアスを書く (TypeScript)
- 理由:仮引数の定義(丸括弧の中身)が長くなりすぎるのを避けるため
-export default function AwesomeComponent({ foo, bar }: { foo: Foo; bar: Bar }): JSX.Element {
+interface Props {
+ foo: Foo;
+ bar: Bar;
+}
+
+export default function AwesomeComponent({ foo, bar }: Props): JSX.Element {
これは個人的な意見なのですが、props の要素が増えてくると関数引数の定義が長くなって見づらくなってしまい、一方、複数行で書いた場合も不格好になってしまうように感じます。そこで、props の型を関数外で定義するようにして分離するようにします。分割代入の要素それぞれに対して型を指定できたら嬉しいのですが、現時点ではそれはできないようでした。引数の多さに応じて型エイリアスの有り無しを柔軟に使い分けるのも良いと思います。
型エイリアスはコンポーネント定義の前に書く
記述場所が離れるのを避けるため、props の型定義はコンポーネント本体の後ではなく直前に書くようにします。また、型名は分かりやすいように Props
などとします。
React.VFC は使わない (TypeScript)
- 理由:以下のリンク先を参照
※2024/3/14 追記
コメントで指摘頂きましたが、TypeScript 5.1 では ReactNode や React.FC の方が好ましかったりするようです。(@honey32 さんありがとうございます!)
It has a controversial history. It used to be the recommended way to type components. Then, it was considered an anti-pattern.
But now, React.FC has changed. Since TypeScript 5.1 and React 18, it's now a perfectly fine way to type your components.
修正のプルリクエスト
このあたりはバージョンが上がるにつれてどんどん変わっていくところかと思いますので、うまくキャッチアップしていけたら良いなと思います。
default export をする
これに関しては諸説あり、どちらでも良いようです。default を使うことで、そのファイルで定義されるべきコンポーネントが明確になると思ったため、こちらを個人的に採用しました。
ただし、default を使う場合は以下のルールを守るようにして、default エクスポートのデメリットを避けられるようにした方が良いでしょう。
- ファイル名とコンポーネント名は必ず一致させる
- import 時にコンポーネント名のリネームを行わない
- named export の場合は as を使用しないということ
- コンポーネント名の変更時は import 文も書き換える
- vscode の機能で実現可能 (2024年2月現在)
- anonymous default export は避ける
また、これらを守るためには、以下の原則を追加する必要があるかもしれません。
- 全てのコンポーネントを異なる名前にする
default export を避けるべき理由はこちらに記載があります。
また、反論としては以下のようなものがあります。
念のためになりますが、この記事での話はあくまでコンポーネントの定義に限った話です。
ユーティリティー関数のようなこまごまとした雑多なものを1ファイルにまとめてnamed exportするとかはまあ全然ありだと思います。
https://zenn.dev/yuhr/articles/668dba202726bf
const とアロー関数ではなく function で書く
- 理由:
export default const
は許可されていないため
基本的にはアロー関数と const を使用した書き方も function を使用した書き方も大きな差異はありませんので、どちらを使っても構いませんし、function を一切使わずアロー関数に統一するという方針もアリかと思います。ですが、export default
と組み合わせる場合は function である必要があるため、個人的にはこちらを採用しました。
アロー関数と function の違いについては以下などで解説されています。自分も先ほどまで同じものだと思っていました。
余談ですが、コールバック関数でアロー関数を使用した方が良い理由については以下の記事などで確認することができます。
named export
を使用する場合は forwardRef
との兼ね合いもあるので最初からアロー関数に統一してしまうというのもアリかもしれません。(後述)
export (または export default) を function と同じ行に書く
- 理由:その方が記述量を抑えられ、冗長性も低くなるため
function を使用する、または named export を使用する状況下においては、これで特に問題ないでしょう。
注意:forwardRef を使う場合
prefer-arrow-callback
を有効にしている場合、forwardRef
の内側で定義された関数コンポーネントはアロー関数に書き換えられてしまいますが、 Component definition is missing display name
という警告を避けるため、この場合だけ prefer-arrow-callback
を無視するようにしています。
※props として _ref
などを使わない場合を想定しています。
interface Props {
foo: Foo;
bar: Bar;
}
// eslint-disable-next-line prefer-arrow-callback
export default forwardRef<HTMLElement, Props>(function AwesomeComponent({ foo, bar }, ref): JSX.Element {
return (
<>
Hello World
</>
);
});
もちろん displayName
を設定しても構いません。アロー関数を使用する場合はそうするのが良いようです。
参考:eslint で保守性を上げる
以下の記事で destructuring-assignment
、function-component-definition
が紹介されています。
以下の記事で import/no-anonymous-default-export
が紹介されています。