React脱初心者への道しるべ #React #設計 #フロント開発 #Tips
この記事は1年以上前に投稿されました。情報が古い可能性がありますので、ご注意ください。
React脱初心者を目指して
Reactが今日のフロント開発に欠かせない存在になっていることは、もはや疑いようのない事実です。
ますます多くのエンジニアがReactを学習し、利用していることでしょう。
本記事は「Reactの基本的な機能は何となく理解し、動くものは一応作れるようになった」というエンジニアが「実務で耐えうる設計や開発の進め方を身につける」ための道しるべです。コード例は一切登場せず、考え方を中心にお伝えしていきます。React初心者の方は「次の一歩」として読み進めていきましょう。
記事は「全体の作り方」と「コンポーネントの作り方」の二部構成になっています。
この記事で身につくこと
- Reactアプリを作る際のオススメ開発環境。
- Reactアプリを、どこからどのように作っていけばよいか。
- Reactアプリを作る際の考え方や進め方。
- Reactコンポーネントの、設計・実装の指針。
- ReactのコーディングTips。
この記事の前提知識
- プログラミング一般に関する基礎知識。
- JavaScript/TypeScript一般に関する基礎知識。
- Web開発一般に関する基礎知識。
- HTML/CSS一般に関する基礎知識。
- Reactで基本的な画面を実装できる程度の基礎知識。
全体の作り方
コンポーネント開発に先立ち、開発環境や全体の構成、考え方などを先にお話しておきましょう。
開発環境を整えよう
1行目のコードを書く前に、まずは開発環境を整えましょう。
エディタ・言語・ツールなど、最低限必要と思われるものを一覧にしました。
それぞれの導入・設定方法についてはWeb上に詳しい記事がたくさんあるので、参考にしてみてください。
ツール・仕組み・技術 | 概要 |
VSCode | 拡張機能を用いて、React開発を効率的に進めることができる。 |
TypeScript | 特にPropsやStateの定義・利用で型システムから強力なサポートが得られる。 |
JSX | プログラムのレンダリング部分をHTML風に記載できる。 |
Yarn | npmより高速で高機能なパッケージ管理ツール。 |
ESLint | 誤りである可能性が高いコードに対し、警告・自動修正を行ってくれる。 |
Prettier | コードのフォーマットを自動的に統一してくれる。 |
Jest | ロジックを自動的にテストできる。コンポーネントにも適用できる。 |
Storybook | ボトムアップ開発を効率的に進めることができる。 |
VSCodeのsettings.json | ESLint・Prettierがファイル保存時に自動実行されるようにする。 |
CI | git push時にESLint・Prettier・Jestが自動実行されるようにする。 |
React Developer Tools | コンポーネントの階層確認や再レンダリングの発生状況確認など、デバッグに役立つ機能が満載。 |
VSCodeの拡張機能
React開発で役立つ、オススメの拡張機能一覧です。
- Auto Rename Tag
- colorize
- CSS to JSS
- ES7+ React/Redux/React-Native snippets
- ESLint
- Jest
- Prettier - Code formatter
機能や使い方は公式ドキュメントやWebでの紹介記事を参考にしてみてください。
環境構築やツールの習得は、慣れるまでは手間がかかりますが、その取り組みは絶対に無駄にはなりません。早く自分のものにするほど、より多くの恩恵を受けることができます。面倒くさがって後回しにせず、時間を取って向き合いましょう。
React公式ドキュメントを読もう
React公式ドキュメントには有用な情報が(日本語でも)分かりやすくまとめられています。まだ読んでいない方は、一通り読んでおきましょう。TypeScript経験者でReact未経験者の場合、読むのに3日程度かかりますが、必ず元が取れる内容です。全ての内容を100%理解する必要はありませんが、React脱初心者を目指すのであれば通読はMUSTです。
Reactの流儀
React公式サイトのReact の流儀には開発の進め方に関する大切な内容が書いてあります。公式サイト全体は流し読みでも大丈夫ですが、このページはしっかりと読み込んで理解しておきましょう。
以下に目次を引用しておきます。
- モックから始めよう
- Step 1: UI をコンポーネントの階層構造に落とし込む
- Step 2: Reactで静的なバージョンを作成する
- Step 3: UI 状態を表現する必要かつ十分な state を決定する
- Step 4: state をどこに配置するべきなのかを明確にする
- Step 5: 逆方向のデータフローを追加する
ボトムアップで実装しよう
「Reactの流儀」にも記載されているとおり、開発はトップダウンでもボトムアップでも進めることができますが、規模によってどちらの方が適しているかが変わります。
シンプルなアプリでは通常トップダウンで作った方が楽ですが、大きなプロジェクトでは開発をしながらテストを書き、ボトムアップで進める方がより簡単です。
これはなぜでしょうか?それは、それぞれの進め方には以下のような利点があるからです。
- トップダウン:詳細な設計を考慮せず、一気に全体を作り上げることができる。
- 一枚の画面(HTML/CSS/JavaScript)を一気に作り上げるイメージ。
- ボトムアップ:全体を作り上げずとも、少しずつ確認しながら進めることができる。
- 必要な末端部品を作り、それを組み合わせた中間部品を作り、徐々に大きい部品を作って、最後は画面に部品を並べていくイメージ。
- 複雑な画面を一気に作り「ここを変えたらあっちが壊れた」などとなるのを避けられる。
従来のHTMLの作成手法に慣れていると、ついトップダウンで作り始めてしまいますが、Reactが必要になるような規模の開発では通常ボトムアップでの開発をするべきです。皆さんが普通のプログラムを作る際も、まずは関数を作って動作を確認し、次にそれらを結合させてまた確認し、最後に全体が動くようにするといったボトムアップアプローチを採用することが多いのではないでしょうか。React開発も、それと同様なのです。
Storybookを使おう
ボトムアップ開発を行うためにはStorybookがとても役に立ちます。作ったコンポーネントをカタログ化しておき、実際の画面に配置しなくても見た目や動作を確認することができます。これは、ボタンなどの末端コンポーネントからそれらを組み合わせた中間コンポーネントやさらに大きなコンポーネントまで、同様に機能します。
見た目や動作がよさそうなコンポーネントを見つけたら、それをプログラムに取り込むためのコードも表示させることができます。このまま貼り付ければ、お望みのコンポーネントを実装中のプログラムに取り込むことができます。
それだけではありません。Storybookを使えばコンポーネントのテストを行うための足掛かりができます。画面に配置せずに色々と確認できるのは言うまでもありませんが、意図せず見た目が変わってしまったら警告を出してくれる「自動テスト」も導入することができます。HTML/CSSで「いつの間にか見た目が壊れていた」という経験をしたことがある方も多いのではないでしょうか。Storybookを導入すれば、そのような悲しい思いは二度としなくてすみます。
フォルダ構成を整理しよう
フォルダ構成については「こうすれば大丈夫」という鉄板はありません。内容に応じて分かりやすい構成を考えていくしかありませんが、ここでは一例として以下のような構成を考えてみました。
コンポーネントのフォルダ構成:
- DOM要素のラッパーコンポーネントはbaseなどのフォルダに配置する。
- 複数画面で共通に使うコンポーネントはcommonなどのフォルダに配置する。
- 全画面共通のレイアウトやヘッダーなどはlayoutsなどのフォルダに配置する。
- 1画面のみで使うコンポーネントは、pagesなどのフォルダに画面名のサブフォルダを作って配置する。
- フォルダ内でコンポーネントをグループ化できる場合はサブフォルダに分ける。
- 画面がタブなどで構成されている場合も同様。
- Storybookの実装ファイルは対象コンポーネントと同じフォルダに配置する。
- 状況の変化に応じてファイルを移動する。
コンポーネント以外のフォルダ構成:
- custom hookはhooksなどのフォルダに配置する。
- hook以外のユーティリティ関数類はfunctionsやservicesなどのフォルダに配置する。
- 画像やテキストなどのデータ類はresourcesなどのフォルダに、適宜サブフォルダを作って配置する。
- 自動テストのファイルは、テスト対象と同じフォルダに配置する。
宣言的プログラミングを採用しよう
プログラミングスタイルには、一般的に採用されている命令的プログラミングと、それとは異なる宣言的プログラミングの2種類が存在します。
- 命令的プログラミング:プログラムから命令によってDOM要素などの対象を参照・操作する。(jQuery方式)
- 宣言的プログラミング:変数を更新するとDOM要素などが自動的に切り替わる。逆に、UIを操作すると変数が自動的に更新される。
jQueryなどで、一つの要素をプログラム内の様々な箇所から参照・変更し、カオスになって扱いきれなくなった経験はありませんか?Reactはそのような命令的プログラミングの弱点を克服するために生み出されたと言っても過言ではありません。宣言的プログラミングをベースとしているReactで命令的プログラミングを使ってしまうと、Reactの利点を十分に活かせなくなり、分かりづらいプログラムになったりバグが発生しやすくなったりしてしまいます。そのため、やむを得ない場合を除き、命令的プログラミングを避け、宣言的プログラミングを採用するようにしましょう。具体的にはrefやdocumentなどは極力使うべきではありません。入力要素の実装には、もちろんcontrolled componentを使うようにしましょう。
全てのパーツをコンポーネント化しよう
Reactでプログラムを開発するというとは、つまりコンポーネントを作るということです。ところで、私たちは一体どのような単位でコンポーネントを分割していけばよいのでしょうか?
経験の浅いReactエンジニアは、画面の全体像から作成し、主に共通化できるところだけを対象としてコンポーネント分割を行おうとします。しかし、それではReactのうまみを生かし切れません。「Reactの流儀」でも説明されているように、「一番最初に」UIをコンポーネントの階層に落とし込んでいくことが大切なのです。コンポーネント化の利点は再利用だけではありません。画面上の「この部分」「この部分の中のこの部分」などと言い表せるところは、全て個別のコンポーネントとして切り出せるはずです。末端は、入力欄やテキストなどもコンポーネント化すべきです。これには標準のHTMLタグに対応する全てが含まれます。
このようにコンポーネント化を進めることによって、以下のような利点が生まれます。
- 詳細が子コンポーネントに委譲・カプセル化され、元のコンポーネントが単純化される。
- DOM構成だけでなくロジック・スタイル・データなど全てにおいて同様。
- この単純化がコンポーネント階層で再帰的に発生する。
- UI(スタイル)や処理の共通化・一元管理がしやすくなる。
- コンポーネントの再利用性が高まる。
- 各コンポーネントのテスタビリティが上がる。
- 無駄なレンダリングを避け、処理を高速化するための足掛かりになる。
確かに、細かく分割すればするだけ手間がかかります。でもそれは、最初だけです。必要なコンポーネントが徐々に揃ってきて「いつものコンポーネントを組み合わせるだけ」で新しい画面をどんどん作成していくことができるようになります。逆に、複数の画面で使われているパーツを後から共通コンポーネント化しようとすると、どうなるでしょうか?同じ内容で作られているはずのものがそれぞれ微妙に異なる作りになっていたりすると、悲劇ですね。複数の開発者が協調無しに複数の画面を一斉に作り始めてしまうと、このようなことになってしまいます。
さて、細かいコンポーネントを大量に作るのは大変骨の折れる作業ですが、実はほとんどの場合、それを自力で行う必要はありません。これについては本記事の終わりの方で説明します。
Tips: デザイナーと意識合わせをしよう
この記事を読んでいる皆さんは、おそらくエンジニアの方が大半でしょう。設計・実装はできてもUIデザインは本業ではなく、プロのデザイナーが担当することも多いのではないでしょうか。そんなときは、デザインを「発注・依頼」して、納品されたデザインに従って「設計・実装」しようとしてもうまく行きません。以下のような点についてUIデザイナーと認識合わせを行いながら、コンポーネントの設計を「一緒に」進めていくようにしましょう。
- 共通で使うパーツはどの単位か。
- 共通で使う各パーツについて、どのようなバリエーション(色・サイズ・一部非表示など)があるか。
- 全画面共通のルール(マージン・色・角丸・ホバー動作など)。
デザイナーとエンジニアが密にやりとりを行うことによって、以下のような利点が得られます。
- お互いに適切なコンポーネント粒度を認識し、適切なデザイン・設計・実装を行えるようになる。
- 実装難易度の高いUIなどについて、デザイン・設計・実装前にデザイナーと相談できる。
- デザイナーも、Figmaでのメインコンポーネント化などを用いて、実装を意識したUIデザインを行うことができるようになる。
コンポーネントの作り方
ここからは、開発の全体像ではなく実際にコンポーネントをどのように作っていくのかについて踏み込んでいきます。
関数コンポーネントで実装しよう
Reactにはクラスコンポーネントと関数コンポーネントがあります。Reactの公開当初はクラスコンポーネントしかありませんでした。関数コンポーネントは、不適切な継承による不適切な設計・実装が発生するのを防ぐために、後から発明・導入されたものです。私たちはクラスコンポーネントではなく関数コンポーネントのみを使うようにしましょう。継承などの「クラス機能」を使いたくなった場合は、React公式のコンポジション vs 継承を参考にすると、関数コンポーネントでどのように実現できるのかが分かります。
必要なPropsだけを受け取ろう
「何を当たり前のことを」と思われたかもしれません。しかし、例えばUserオブジェクトを受け取って、実際に使うのはnameプロパティのみというようなことはありませんか?コンポーネントのPropsでは、本当に必要なデータだけを受け取るようにすることによって、以下のような利点を得ることができます。
- 無駄なレンダリングを避けやすくなる。
- コンポーネントの責務が分かりやすくなる。
- コンポーネントの再利用性が高まる。
- コンポーネントのテスタビリティが上がる。
ただし、オブジェクトではなく個別のデータとして受け取ることによって、意味が分かりにくくなる危険性があります。その場合はnameではなくuserNameという名前にするなど、工夫するとよいでしょう。また、大量のデータが必要な場合はそれをオブジェクト化したりデフォルト引数を設定したりすると使いやすくなるかもしれません。ただ、大量のデータが必要なのはそもそもコンポーネントの責務が重すぎる可能性があるので、分割を考えてみてもよいかもしれません。
グローバルな状態管理にはRecoilを使おう
ログイン情報などのグローバルな状態管理が必要になることがあります。Reactにはcontextという仕組みがありますが、Recoilの方がより簡単に扱えるでしょう。
PropsによるStateの引き回しが煩雑になりすぎる場合は、必要に応じてRecoilの導入を検討しましょう。Recoilの導入によって中間階層のコンポーネントを単純化することができます。なお、システム全体での状態管理だけではなく、一部に閉じている場合でも、導入を検討する余地があります。
「グローバル=悪」という考えもありますが、適切に使えば強力な武器となってくれることでしょう。
ロジックはコンポーネント関数の外に書こう
私たちはReactでコンポーネント開発をします。メインの作業は関数コンポーネントの作成です。関数コンポーネントは関数なので、ロジックを書くことができます。でも、ちょっと待ってください。コンポーネント関数の主要な役割はレンダリングです。それ以外はレンダリングを行うための補助です。コンポーネント関数の中に本来の役割とは関係が薄い処理ロジックを多く書いてしまうと、その役割が分かりづらくなってしまいます。
データ処理・判断などのロジックはコンポーネント関数から切り出して別の関数に実装しましょう。コンポーネント関数には、それらの呼び出しとDOMのレンダリングのみを実装しましょう。理想的なコンポーネントには、少数の関数(hookを含む)呼び出しがあり、残りはDOMのレンダリングのみになっているはずです。コンポーネント関数は自身の責務(レンダリング)に集中し、それ以外の些末な処理は別の関数に任せるようにするのです。このようなコンポーネントは「必要なデータを取得してそれを表示する」といったように、とても単純で分かりやすい作りになります。オブジェクト指向の「単一責任の原則」に似ていますね。
また、切り出された処理ロジックは、コンポーネント関数内にある場合に比べて再利用性やテスタビリティが上がるというメリットもあります。
意味のある単位でcustom hookとしてまとよう
例えば、コンポーネントで外部APIからデータを取得して、その結果を加工して表示するような場合を考えてみましょう。そのような場合には、それを全て行うcustom hookを作成して呼び出すようにします。内部の処理は必要に応じて細かく関数化していきますが、コンポーネントから直接末端の関数を順に呼んでいくのではなく、コンポーネントが必要とすることをまとめて処理するcustom hookを意味ごとに作成しておき、コンポーネントからはそれを呼ぶようにするのです。これによってコンポーネントが単純化され、hookや各関数の再利用性・テスタビリティが上がることになります。
デザインパターンで言うと「Facadeパターン」ですね。
データはコンポーネント関数の外に書こう
メニュー項目の一覧・コンボボックスの項目一覧など、プログラムの中には様々な固定データが存在します。コンポーネントはこれらのデータを使って画面に表示することになりますが、これらのデータはコンポーネント関数の外に書くようにしましょう。理由や効果は、前節までに述べてきた内容と同じです。コンポーネントが単純化され、データ自体の再利用性やテスタビリティが上がることになります。
なお、個別コンポーネントで必要なデータは、コンポーネント関数の外に書きますが、同じファイル内に書いてカプセル化し凝集度を上げるようにしましょう。
スタイルはコンポーネント関数の外に書こう
CSSの記述には、いくつかの手法があります。ここでは、それぞれの特徴を見てみましょう。
手法 | 特徴 |
CSSファイル |
|
CSS Modules |
|
inline style |
|
CSS in JS |
|
この特徴を見ると、基本的にはCSS in JS一択であることが分かるでしょう。CSS in JSの利用にはmuiのstyled componentなどを利用することになります。また、muiのsxはinline styleの分かりやすさとCSS in JSの力強さを併せ持った強力な武器として利用できます。
CSS in JSのおかげで、これから私たちは、スタイルがうまく適用されないという問題に悩まされることはありません。
レンダリング部分のロジックは最小限にしよう
関数コンポーネント内で、DOMのレンダリング部分は可能な限り簡潔にしましょう。具体的には、下記がよくあるパターンです。
条件部分は簡潔に
条件に応じてレンダリング内容を変更したい場合は論理演算子(&&や||)を使ったり条件演算子(?:)を使ったりして条件付きレンダーを行うことがよくあります。
この際に、条件部分が複雑になるのであれば、その部分を関数として切り出すようにしましょう。それによってDOMレンダリング部分の可読性が上がるだけでなく、条件部分の再利用性やテスタビリティも向上します。
スタイルの適用を条件に応じて選択的に実施する場合も同様です。
mapでのレンダリング対象は切り出す
配列内の各要素に対応するDOM要素をmapを使ってレンダリングすることがよくあります。
この場合は、mapでのレンダリング対象部分全体を1つのコンポーネントとして切り出しましょう。これによりDOMレンダリング部分がスッキリして分かりやすくなるとともに、切り出したコンポーネントの再利用性・テスタビリティが上がります。また、無駄なレンダリングを避けて処理を高速化するための足掛かりにもなります。
コンポーネントには表示名を設定しよう
必要に応じてdisplayNameを設定し、デバッグの助けにしましょう。muiのstyled componentを使う場合は、options.labelの活用も視野に入れましょう。いずれも、React Developer Toolsでコンポーネントを見分けやすくなります。
「1ファイル=1コンポーネント」にしよう
カプセル化や単一責任の原則を推進するため、原則として「1ファイル=1コンポーネント」としましょう。
ファイル内の構成は、ファイルの上から以下の順で記述すると分かりやすくなります。
- Props以外の型定義。
- Propsの型定義と、それに使うための型定義。(コンポーネントの直前に書くと分かりやすくなる)
- styled前のコンポーネント関数。
- custom hookや通常の関数。(意味ごとにまとめて記述)
- スタイル関連の関数(スタイルの選択処理など)やデータ(「RED="#FF0000"」など)。
- sx用スタイルオブジェクトの定義。
- styled呼び出しと、その引数のスタイル定義。
- スタイル以外のデータ定義。(画面で表示するメニュー項目の一覧など)
Tips: 車輪の再発明は避けよう
コンポーネントを作るのは大変です。なるべくなら既存のコンポーネントを使って楽をしたいですよね。Reactの世界にはmuiに代表されるような「コンポーネントライブラリ」と呼ばれるものが存在します。コンポーネントライブラリには入力要素・レイアウト・ポップアップなどの「みんながよく使うコンポーネント」が豊富に含まれています。これらを利用することによって開発の手間が軽減できるだけでなく、バグが発生する可能性を抑制し、保守対象のコードも少なくすることができます。学習コストはかかりますが、開発効率を大幅に上昇させることができるため、知っておいて損はないでしょう。
また、コンポーネントライブラリの他にも、npmやGitHubには多くの便利なコンポーネントが公開されています。システム固有のコンポーネントは自作するしかありませんが、他の人が作っていそうなコンポーネントであれば自作する前に一度検索してみることをオススメします。
Happy React Life!
ここまでReact初心者を脱するための様々な知識を見てきました。
もしかして、Reactは複雑で難しいと感じてしまいましたでしょうか?確かに、簡単に習得できるようなものではありません。ただ、本当に難しいのはフロント開発自体なのです。複雑なフロント開発を少しでも楽に・うまく進めるための技術がReactです。Reactが大変だからといって逃げていると、もっと大変な思いをすることになってしまうかもしれません。私たちの救世主であるReactと仲良くなって、楽しい開発を継続していきましょう。stateを変更するだけで画面に反映された、あの日の感動を忘れずに!