Vue.js民の皆さまこんにちは。
この記事では、Atomic DesignをVue.jsと組み合わせた際に @nekobato がエンジニアリング目線で勘所だと感じたことを述べます。
Atomic Design
参考文献です。
Atomic Design | Brad Frost
Webフロントエンドのエンジニアリング文脈において、Atomic Designの素晴らしい革命の一つは、依存の流れを単一方向にした昨今のWebアーキテクチャをデザイン構造に落とし込んだことだと個人的には考えています。
Webフレームワークのコンポーネント設計は多くのプロジェクトで様々なオレオレ設計が生まれてきましたが、Atomic Designが有名になったことで大きな流れが作られました。
Atomic Designを使う上の勘所
今回はVue.js単体とAtomic Designを組み合わせた場合の話をします。
Vuex, Nuxt, Apollo-vueなどと組み合わせた場合の深掘りはしませんが、Vue自身の構造は組み合わせたとしても変える必要があるとは感じません。
依存は上から下に向く
Atomic Designでのコンポーネント依存関係は、Vueの持つ原則Props in / Events outと大変相性が良く、下の層が上の層を感知しないという設計が容易に達成できます。
基本原則としてVue Componentsは、子にはPropsを渡し、親にはEventsを渡します。そう聖書にも書いてある。
https://vuejs.org/v2/guide/components.html#Passing-Data-to-Child-Components-with-Props
https://vuejs.org/v2/guide/components.html#Sending-Messages-to-Parents-with-Events
実際にAtomic Designで使ってみる場合、Templates, Organisms, Molecules, AtomsはPropsで渡ってきたデータを使用し、下にそのままないしは加工したデータを流すという振る舞いで統一します。
こうすることで、単体のコンポーネントテストが容易になります。
デザインに関しても、Moleculesは自身がリストで並んだ際のmarginは考慮せず、Organismsで宣言するなど、独立したものとして設計することで柔軟な使い回しが可能になるでしょう。
ページコンテンツはPagesにデータが入るまで確定しない
前述の通り、Templates, Organisms, Molecules, AtomsはPropsからデータを入力、下位のコンポーネントにデータを流すという単純な構造を持っています。ではどこで元のデータを持つかといえば、
Atomic Designの解説記事でも表現されている通り、Pagesにデータが入るまでコンテンツデータは確定しません。
この点は構造のカスタマイズによって変化しますが、基本データはPagesに流し込まれます。
TemplatesやOrganismsが直接データへのアクセスを持ってもええやん? というカスタマイズ方法もありますし、実際そちらの方がコード的に単純化できる場合もあります(というか専らそう)。
しかし特定の層の振る舞い、そして何をテストするのかを統一することで、後々の読み解きやすさ・テストのしやすさを提供してくれます。
例外として、恒久的に確定した要素(HeaderMenu, FooterのPageListなど)はMolecules, Organismsの時点で表現します。
<template>
<div>
<Layout v-bind:contents="contents" />
</div>
</template>
<script>
export default {
...
data: () => ({
contents: {
itemList: [...],
avator: {
name: 'nekobato'
},
hogeList: [...],
}
}),
...
}
</script>
<template>
<div>
...
<AvatorContainer avator="contents.avator" />
// 以下Presentional Componentまでバケツリレー
...
</div>
</template>
PagesからAtomsまでデータを受け渡すバケツリレーが迂遠なことは確かですが、これはTemplates以下が持つ依存をpropsによる入力、Eventsによる出力という単純なパターンにすることで、テストが書きやすくなることと、後々にコンポーネント挙動を変更しやすくなることに効果を発揮します。
Pagesがページコンテンツの全てを受け持つということは、Pagesが持つdata
はVuexのStoreのように巨大なものになります。基本的には加工されていないドメインデータを列挙する形になるため、複雑さはドメインデータの複雑さとイコールです。
また、非同期なデータをfetchした際の受け手もPagesです。
要するにVuexと似たような振る舞いをPagesが行うことになります。
SPAで大きなオブジェクトがページをまたぐ必要があったり、EventsとDataの流れにレールが欲しい時はAtomic Designのレールにも乗りつつVuexを活用すると良さそうです。まだそこまで巨大なものを構築しようとしたことがない。
よく悩むところ
Organisms, Moleculesの分け方
プロジェクトにOrganisms, Moleculesの粒度と区分をどうするか悩むこと場面は非常に多いです。
ですが、個人的には好きにカスマイズすれば良いと思っています。
はっきりとContainer Componentであると言えるのはTemplatesとPages。
対して完全にPresentational ComponentなのはAtomsで、Organisms, Moleculesの2つはどうしてもPresentationalとContainerの中間部の役割を吸収するためのものになります。
作成するWebサービスと比べて階層が多いと思うならOrganismsを削除して運用しても良いし、2つでは階層が薄いと思うならMacromoleculesみたいな名前を追加しても良いと思います。
実際、Atomic Designを現実的なReact構成用にカスタムした構成でAtomic Componentsという例もあります。こちらはAtoms以外完全に名前が変わっていますね。
重要なのは、依存が上から下(Pages -> ... -> Atoms
)へ向いていること。下から上へ、または同じ層への依存は発生しないことです。
イベントハンドルのレールは無い
大本のAtomic Designについての記事(&本)がWebフレームワークの話をしていないので、Event Handlingをどこでするか? という例は挙げられていません。
しかしPagesがコンテンツデータを持つ場合、データに関するハンドリングはPagesに巨大なエラーハンドリング機構が発生するのは仕方ないと感じています。ここを構造化して管理したい場合はやはりVuexの出番になるでしょう。
設計変更した際のチェック
Atomic Designの構造上、ボタンの挙動を一つ変えるだけでも全てに影響があります。その際に全ての影響範囲を人が覚えておくのは無理な話なので、変更を信頼できる仕組みが必要です。
storyshotsまたはsnapshotで全てのUIをカバーし、変更がdiffに現れるようにするまでがAtomic Designと言えるでしょう。
実践的なAtomic Designの今後
Atomic Designを現在のWebフレームワークの上で実践的にどう使うのかは議論の余地があります。
Vue.jsにおける実践的なバイブルと言えるほどの文献もまだ現れていない(多分)ので、Atomic Designのどのルールを採用することで何の恩恵を受けて、何が失われるのか?ということをピックアップして採用したらよいと思います。