今回の記事について
Vue.js / Nuxt.jsにおけるコンポーネント設計を考えてみました
昔筆者が数人で使うような社内ツールをVue.js / Nuxt.jsで実装した時に、コンポーネント設計についてあまり考えておらず適当に作成していました
1つのコンポーネントでコード量が増えてしまったり、componentsディレクトリ配下にずらずらとコンポーネントが並び同一ディレクトリでコンポーネントの粒度がまちまちで見栄えが悪い状態となっていました
その状態で新規に開発メンバーがアサインされてソースを見てもらった時には申し訳なさでいっぱいになってしまったため、一度コンポーネント設計について考えてみることにしました
どういうコンポーネント設計手法があるのかをまず調べてみて、その後で最終的に自分だったらどのように設計していくかをまとめていきたいと思います
最後に記述する自分なりのコンポーネント設計についてはまだ実装したことがないためやってみるとつまづきなどあるかもしれません、その辺りご容赦いただけたらと思います
Presentational Component / Container Componentについて
説明
コンポーネント分類のパターンとして「Presentational Component」と「Container Component」の2つに分けて考えるパターンです
Dan Abramov氏が考えたReactの実装パターンらしいです
Presentational and Container Components
2019年にDan Abramov氏によって元記事が更新され、現在はReact Hookが使えるからこの実装パターンはおすすめしないと書かれているみたいです、ですが考え方としては参考になる部分があるためこの記事でも紹介したいと思います
コンポーネントの分類
Presentational Component
- 見た目に関する責任を負う
- 内部にPresentational ComponentやContainer Componentを持つことができる
- DOMマークアップやスタイルを持つ
- propsで受け取った値の表示はするが、データを自身で勝手に読み込んだり改変しない
- StoreやRouterなどアプリケーションの機能に依存しない
- 使用例:Page, Sidebar, Story, UserInfo, List
Vueコンポーネントの場合でもPresentational Componentは受け取った値を表示して、アクションが起こった場合は$emitを使って上位のコンポーネントに処理を委ねるという形になります
Container Component
- コンポーネントの振る舞いに関する責任を負う
- 内部にPresentational ComponentやContainer Componentを持つことができる
- DOMマークアップやスタイルは持たない
- データ及びデータを扱うためのファンクションをPresentational Componentに渡す
- Storeのデータやcommit/actionを扱う
- 使用例:UserPage, FollowersSidebar, StoryContainer, FollowedUserList
このようにContainer Componentはデータの読み込みやアプリケーション内の機能にアクセスすることができ、読み込んだデータや関数をpropsとしてPresentational Componentに渡す流れになります
下記は一つのページをどのようにPresentational ComponentとContainer Componentに分類していくかのイメージ図です
UIコンポーネント毎にPresentational Componentを作成し、データの読み込みや更新処理などがあるコンポーネントには前段にContainer Componentを配置する、入力項目の更新などイベントが行われた時は上位のContainer Componentにemitさせることで振る舞いはContainer Componentに寄せています
利点
- 振る舞いとUI部分を分割することができるため、実装が理解しやすくなる
- 振る舞いに依存しないため再利用性が高い
- デザイナーがアプリケーションのロジックに触ることなく書き換えを行うことができるため分業しやすい
課題
このやり方における分類の場合、見た目と機能の2種類への分類となっているため粒度が荒くなってしまう可能性があります
例えば「投稿フォーム全体のコンポーネント(機能全体のコンポーネント)」と「テキストボックスのコンポーネント(機能の中の1部分のコンポーネント)」の2つがあった場合に、粒度が違うものが同じディレクトリに配置されることになるため少し気持ちが悪いのと、1ディレクトリにずらずら並ぶのでコンポーネントが探しづらいかもしれません
Atomic Designについて
説明
Atomic DesignはUI/UXデザイナーのBrad Frost氏が考案・書籍化されたデザインシステムで、特徴としては小さいUIコンポーネントを組み合わせて大きなコンポーネントを作っていくUI設計手法となっています
書籍の内容はすべてWeb上で公開されているため以下のサイトで見ることができます
デザインシステムとは、デザインの原則、概念、ガイド、コンポーネントなど、デザインに関するあらゆるルールを定めたもののことを言います。 スタイルガイドやコンポーネントライブラリ(パターンライブラリ)はデザインスタイルの一部にあたります。
Atomic Designはフロントエンドのコンポーネント設計思想のように語られることもありますが、あくまでもデザインの設計思想です
なので実装(データやロジックの責務など)については触れられていないため、データの呼び出しやStoreをどこで読み込むかなどコンポーネントの振る舞いについてどうするかというと、それは個人で考えていくことになります
Webで調べた際の記事の中には実装について触れられているものもありますが、基本的には後付け(個人で考えた設計手法)となっているはずです
元々はデザイン手法として提案されたAtomic Designですが、考え方がフロントエンドのコンポーネント設計と相性が良いため近年「Atomic Design」と「コンポーネント設計」がセットで広まってきているのではないかと考えています
コンポーネントの分類
Atomic DesignではUIの構造を次の5段階に分類しています
- Atoms(原子)
- リンクやボタンなどこれ以上分解することができないUIパーツ
- Molecules(分子)
- 2つ以上の原子を組み合わせたシンプルなUIパーツ
- ユーザーの動機を促すもの(何のためにボタンをクリックするか、何のためにテキストを入力するか動機に応える)
- Organisms(有機体)
- AtomsやMoleculesを組み合わせて構成される
- 独立して成立するコンテンツを提供する
- 幅を固定しないとデザイン通りにならなかったり使い方が限定的なもの
- Templates(テンプレート)
- いくつかのOrganisms・Molecules・Atomsから成り立っているUIの骨組み
- Pages(ページ)
- Templatesに実際のコンテンツを適用したもの
UIの階層構造
画像引用元:Atomic Design Methodology
原子が組み合わさって分子を構成し、分子が集まって有機体を構成する自然界のモデルをUIのコンポーネント設計に適用しています
ちなみにAtoms・Molecules・Organismsと科学用語が続いていて、TemplatesとPagesだけ科学用語ではないことにも理由があり、自然界のモデルに習ったコンポーネント分類の概念については開発者だけが知っておけばよく、TemplatesとPagesについてはプロジェクトマネージャーや経営者、クライアントと話す際にも説明する必要がある概念だという意味が込められているそうです
ページの要素をAtoms・Molecules・Organisms・Templates・Pagesの5つに分類した例が以下の図になります
画像引用元:Atomic Design Methodology
利点
- 再利用性が高い
- 開発メンバー内で作業分担がしやすくコンフリクトが減らせる
- デザイナーと意思疎通がしやすい
- ディレクトリ毎にコンポーネントの粒度が揃う
- Atoms層で統一されたデザイン・コンセプトに従っていればUIに統一感を出しやすい
課題
5つに分類する手法の課題としてはMoleculesとOrganismsのどちらに含めて良いかわからないということが頻繁に出てきます
これはMoleculesもOrganismsもどちらも複数のコンポーネントを組み合わせて作るため、それぞれがどういったものかの説明があっても、人によって解釈が変わってしまうからです
この問題に対する考えかたとしてはOrganismsが独立して存在できるコンポーネントかどうかで考えるのが良さそうです
例えば「広告バナー」や「コメント投稿フォーム」はどのページにもそのまま配置できるということからOrganismsと判断します
それに対して「シェアボタンリスト(Twitter, はてブなど)」や「投稿リアクション(いいねやコメント数など)」は複数のコンポーネントの組み合わせではありますが、それが独立して機能するかでいうとそうではなく、対象の記事があって初めてその記事に対する機能となるため、独立しているとは言えません
ここまで描いてもまだまだ判断が難しい部分があります、そうなってくるとAtomic Design的に何が正しいのか?ということを追い求めるよりも、開発チーム内で納得ができるように議論をした上で判断していくのが良いかと思います
お応用例紹介
2つのコンポーネント分類方法を紹介しましたが、それらの考えを元に派生してできた手法や、具体的に定義した記事を紹介します
その①
BASEのVue.jsコンポーネントの設計について登壇してきました
こちらで記載されているVueコンポーネント設計手法です
Atomic DesignとContainer Componentの考え方を元に役割を大きく4つに分類しています
- Container Component
- Presentational Component
- Common Presentational Component
- Atom Component(UIライブラリ)
この記事で言いたいこととしては「コンポーネントの良さは共通化ではなく責務の分離」とあり、「Presentational Component」「Common Presentational Component」「Atom Component」と似たものが3つ出てきますが使い分けもしっかり定義されていました
その②
Atomic Design ベースのコンポーネント設計を考えてみた
こちらではAtomic Designにデータの流れやストアに関する制約も定めています
またモーダルやアコーディオンなどアニメーションなどのインタラクションを表現するコンポーネントとして5分類にプラスして「Providers」カテゴリというものを定義しています
制約については表にしてまとめられているので見やすいものとなっていました
自分だったらどうしていくか
前提
ここまでで「Container Component / Presentational Component」と「Atomic Design」について説明しつつ他の人が作成した設計手法についても紹介しましたが自分だったらどうするのかについてもまとめておきます
ここでは数ページ程度で構成される小規模サイトを作成する時のコンポーネント設計なので中規模以上のサイトを作成する場合の設計手法はまた別途考えるのが良いかと思います
最初にも書きましたがまだ過去の経験を踏まえた思いつき程度の意見をメモ程度に残す感じなので実際にその通りに実装したことはありません、参考程度に聞いてもらえればと思います
またNuxt.jsの場合、バージョンを12.3以上にすると、コンポーネントのインポートが不要になるのでディレクトリ移動をしたくなった時にスムーズに移動ができるのでできるならばこれ以上のバージョンにするのが良いかもしれません
方針
応用例①に近いですが以下のようなディレクトリ構造を考えています
概念イメージ
ディレクトリ構成例
components/
├── ページ名
│ ├─ ContactFormContainer.vue
│ └─ ContactFormPresentational.vue
├── common
│ │─ Header.vue
│ └─ Footer.vue
└── atoms
└─ Button.vue
- ディレクトリの分類は「ページ名」「common」「atoms」とする
- ページ名
- ページ毎に1つのディレクトリを作成する(Nuxt.jsだとpages以下の各ファイルに対応するディレクトリを作成する)
- 再利用性は求めず、実装や責務の分離を大事とする
- Atomic Desingで言う所のOrganismsにあたる
- このディレクトリ内でContainer ComponentとPresentational Componentに分類
- Containerは必要な時のみ作成する
- common
- 複数ページで使い回されるUIコンポーネント
- 振る舞いについては記載せず、イベント発行時はemitにて上位Container Componentで処理を行う
- common以下をContainerとPresentationalで分けるかはやってみた時に判断
- atoms
- Atomic Designのatomsと同じくこれ以上分割できないUIパーツ
- ページ名
- 画面操作などによるイベント発火時にはemitによって上位Container Componentで処理を請け負う
コメントを書く
コメント一覧