Reactを使ったプロダクトリニューアル:コンポーネント設計編 - eiicon Tech ブログ #2
eiicon noteをご覧のみなさま、こんにちは!開発Gフロントエンドエンジニアの上野です。2023年4月にリニューアルした「AUBA for Admin」についてご紹介するシリーズの2回目として、この記事ではReactのコンポーネント設計についてご紹介させていただきたいと思います。
ディレクトリ構成
前回の記事でご紹介させていただきましたが、Reactのディレクトリ構成は下記のようになっており、細かい部品としてのコンポーネントは「components」ディレクトリに格納し、実際にページに表示する時は各コンポーネントを「pages」ディレクトリから呼び出して使用しています。このディレクトリの中にコンポーネントをどういった粒度で作っていくか設計をする際に、今回は「アトミックデザイン(Atomic Design)」と「コンテナ・プレゼンテーションパターン(Container / Presentational Pattern)」という2つの手法を組み合わせて設計を行いました。まずはこれらの手法についてご紹介させていただきます。
auba-admin
├ app
│ └ javascript
│ └ src
│ ├ .storybook
│ ├ components ← コンポーネントを格納
│ ├ config
│ ├ graphql
│ ├ layouts
│ ├ libs
│ ├ pages ← ページを格納
│ ├ providers
│ ├ store
│ ├ styles
│ ├ types
│ └ utils
├ public
├ esbuild.js
├ package.json
├ yarn.lock
└ tsconfig.json
アトミックデザイン
「アトミックデザイン」はWebデザインと開発の方法論で、より効率的で一貫性のあるユーザーインターフェース(UI)を作成するための一連のプロセスとツールを提供します。このアプローチは、デザインシステムを化学の構成物質に基づいて階層的に構造化することにより、再利用可能なコンポーネントの構築を促します。
「アトミックデザイン」では下記の5つのレベルにコンポーネントを分けて構成していきます。
原子(Atoms)
これらはUIの基本的な構成要素で、例えばボタン、入力項目、タイトルなどのような部品を指します。分子(Molecules)
これらは原子を組み合わせて作られます。例えば、ラベル(原子)と入力項目(原子)を組み合わせた検索フォーム(分子)などがこれに該当します。有機体(Organisms)
これらは分子(または原子)を組み合わせて作られます。1つの有機体内には異なる種類の分子が含まれることがあります。例えば、ヘッダーはロゴ(原子)、ナビゲーション(分子)、検索フォーム(分子)などから構成される可能性があります。テンプレート(Templates)
これらはページレイアウトを定義し、どのように有機体、分子、および原子が配置されるべきかを示します。しかし、具体的なコンテンツはまだ提供されていません。ページ(Pages)
これは最終的な製品であり、具体的なコンテンツがテンプレートに追加されたものです。
「アトミックデザイン」では再利用可能なコンポーネントを作成し、それらを組み合わせて複雑なインターフェースを構築することが可能です。これにより一貫性と効率性が向上し、メンテナンスも容易になることが主なメリットだと思います。
コンテナ・プレゼンテーションパターン
「コンテナ・プレゼンテーションパターン」はコンポーネントを「コンテナコンポーネント」と「プレゼンテーションコンポーネント」という2種類に分けて設計をしていく手法です。コンポーネントの責任をはっきりと区分けすることでコードの再利用性とメンテナンス性を向上させることができます。
プレゼンテーションコンポーネント(Presentational Components)
プレゼンテーションコンポーネントは、主に見た目(UI)を扱います。これらのコンポーネントは直接状態を管理せず、データをどのように表示するか、またユーザの操作をどのように処理するかに焦点を当てます。一般的には、propsを通してデータを受け取り、何も(または少なくとも非常に少ない)知識を持つことなくデータを表示します。また、ユーザからの入力やアクションに応じて、親コンポーネントまたはコンテナコンポーネントに情報を送信するためのコールバックを提供します。
コンテナコンポーネント(Container Components)
コンテナコンポーネントは、ビジネスロジックや状態管理を担当します。これらのコンポーネントは、どのように動作するかを決定し、必要なデータをフェッチし、そのデータをプレゼンテーションコンポーネントに供給します。基本的には、データのソースとして機能し、そのデータを子のプレゼンテーションコンポーネントに渡します。これにより、プレゼンテーションコンポーネントは純粋に表示の役割に集中することができます。
この手法では、「プレゼンテーションコンポーネント」は状態管理から分離されているため、他の部分でも再利用しやすくなります。また、ビジネスロジックとUIが明確に分離されているため、コードはより読みやすく、メンテナンスしやすくなったり、それぞれを個別にテストすることが容易になるという利点があります。
「AUBA for Admin」のコンポーネント構成
前述の2つの手法を踏まえて、弊社の「AUBA for Admin」ではコンポーネントを格納するディレクトリは下記のような構成になっています。
src
├ components
│ ├ common
│ │ ├ Footer
│ │ ├ Header
│ │ └ etc…
│ ├ layout
│ │ ├ Inner
│ │ ├ SectionWrapper
│ │ └ etc…
│ ├ pages
│ │ ├ top
│ │ │ ├ TopPageContents.tsx
│ │ │ └ etc…
│ │ └ etc…
│ ├ transition
│ │ └ FadeTransition
│ └ ui
│ ├ accordion
│ ├ alerts
│ ├ buttons
│ │ ├ BaseButton
│ │ │ ├ index.stories.tsx
│ │ │ ├ index.test.tsx
│ │ │ ├ index.tsx
│ │ │ └ style.ts
│ │ ├ IconButton
│ │ ├ UnstyledButton
│ │ └ etc…
│ └ etc…
├ pages
│ ├ top
│ │ ├ App.tsx
│ │ └ index.tsx
│ └ etc…
└ etc…
「components」ディレクトリの直下には5つのディレクトリが配置されており、それぞれの役割は下記のようになっています。
components/common(有機体 / プレゼンテーションコンポーネント)
全体で共通で使用するヘッダーやフッターなどのコンポーネントを格納しています。アトミックデザインにおける「有機体」と「プレゼンテーションコンポーネント」としての役割を持ちます。components/layout(原子 / プレゼンテーションコンポーネント)
ページにコンテンツを表示する際のラッパー要素など主にページレイアウトに関するコンポーネントを格納しています。アトミックデザインにおける「原子」と「プレゼンテーションコンポーネント」としての役割を持ちます。components/pages(テンプレート / コンテナコンポーネント)
ページに表示させるためのコンポーネントを格納しています。実際のURLベースでディレクトリが作られており、各ディレクトリに配置されたコンポーネントはそのページ専用で使用しています。基本的に後述する「pages」がビジネスロジックや状態管理に関する責務を負っていますが、場合によってはこのコンポーネントもAPIなどとのやり取りを行うため、アトミックデザインにおける「テンプレート」と「コンテナコンポーネント」としての役割を持ちます。components/transition(原子 / プレゼンテーションコンポーネント)
モーダルを表示する際のフェードイン・フェードアウトなどアニメーションに関するコンポーネントを格納しています。アニメーションライブラリとして「Framer Motion」を使用しており、主にアニメーションの設定を定義したラッパーコンポーネントが配置されています。アトミックデザインにおける「原子」と「コンテナコンポーネント」としての役割を持ちます。components/ui(原子、分子、有機体 / プレゼンテーションコンポーネント)
ボタンや入力項目などの見た目に関する小さな部品を格納しています。主にテンプレートとしての「index.tsx」、スタイルを定義する「style.ts」、Storybookで使用する「index.stories.tsx」、Jestでテストを実施するための「index.test.tsx」の4つのファイルが格納されています。アトミックデザインにおける「原子」、「分子」、「有機体」と「コンテナコンポーネント」としての役割を持ちます。pages(ページ / コンテナコンポーネント)
各ページで使用するReactのフロントファイルを格納しています。実際のURLベースでディレクトリが作られており、各ディレクトリにはそれぞれ「index.tsx」と「App.tsx」が配置されています。「App.tsx」の中で「components/pages」に格納されている「有機体」を組み合わせて実際のページレイアウトを定義しています。APIとのCRUD処理なども記述しているため、アトミックデザインにおける「ページ」と「コンテナコンポーネント」としての役割を持ちます。
さいごに
今回は弊社プロダクトのコンポーネント設計について紹介させていただきました。「アトミックデザイン」と「コンテナ・プレゼンテーションパターン」という2つの手法を組み合わせたコンポーネント設計を行ったことで下記のような利点があったかと思います。
「アトミックデザイン」を用いることでデザインイメージからコンポーネントを切り出す作業がスムーズに行えた
「コンテナ・プレゼンテーションパターン」を用いることで各コンポーネントの役割が明確になった
「アトミックデザイン」を利用することで見た目としてのコンポーネントの切り分けが行いやすくなりました。しかしそれだけだとPropsの流れやロジックをどこに記述するかなどの設計で混乱していたかなと思います。合わせて「コンテナ・プレゼンテーションパターン」を用いることで、そのような部分がクリアになって、よりスムーズにコンポーネント設計が行えたと思います。