最近、Atomic Design(アトミックデザイン)を用いたコンポーネント設計、実装を担当する機会がありました。
その経験をもとに、Atomic Designの簡単な概要、そして取り入れて感じたメリット、デメリットについて書いていこうと思います。対象読者はこんな感じ。
・Vue、Reactを書いたことがある
・Atomic Design?全然知らん
・名前だけ聞いたことあるけど、中身はよくわからない
参考になれば幸いです。
Atomic Designの概要
Atomic Designについて深く語れるほどの知識はないのですが、ざっくり説明すると、「ページを構成する要素をパーツ(コンポーネント)として捉え、ページデザインをそれらの組み合わせによって表現する」みたいな感じになるかと思います。
パーツはその粒度によって5つの単位に分類され、それぞれ「Atom」、「Molecule」、「Organism」、「Template」、「Pages」と呼ばれます。
(こちらの画像は[Atomic Designを分かったつもりになる](https://design.dena.com/design/atomic-design-を分かったつもりになる/)から引用させていただきました)Atom(原子)は文字通り、それ以上細かく分割できないパーツ(Buttonなど)を意味し、Molecule(分子)はAtom要素がいくつか組み合わさって構成されるパーツです。
そしてOrganism、Templateは複数のAtomやMoleculeによって構成されるパーツとなります。
言葉で説明するとなんのこっちゃ?って感じですが、実際のデザインをもとに見るとある程度わかりやすいかと。
(こちらの画像はVue + Nuxt + Serverless の開発で得た知見 (主につらみ) から引用させていただきました)
これはよく見るカード型のコンポーネントで、カードコンポーネントはTitle、Contentなどから構成されています。TitleはAtom、ContentはOrganismといった感じでしょうか。
また、カードコンポーネント自体もページを構成するパーツの一部(Organism)で、こういったコンポーネントを配置することでページデザインを表現していくことになります。
このカードコンポーネントをコードに落とし込んでみると、こんな感じになるかなと思います。
<template>
<div>
<ArticleCardImage
:src=""
:width=""
:height=""
/>
<ArticleCardContent
:title=""
:over-view=""
:data=""
:token-amount="tokenAmount"
/>
</div>
</template>
<template>
<div>
<ArticleCardContentTitle>{{ title }}</ArticleContentTitle>
<ArticleCardContentView>{{ overView }}</ArticleContentView>
<ArticleCardContentData :data="data"/>
<ArticleCardContentTokenAmount :token-amount="tokenAmount">
</div>
</template>
(あくまでイメージなので、実際の実装とは違います)
初見だと俺の知ってるHTMLと違う...ってなりそうですね笑
Atomic Designについての解説はもっと詳しい記事がたくさんあるので、ぜひそれらを参照してください。
Atomic Designでコンポーネント設計をするメリット
さてこのAtomic Design、コンポーネント設計に取り入れると何が嬉しいのかについてです。自分が実装していて感じたメリットは大きく3つあります。
・コンポーネント設計の指針にできる
・データ(props)の流れを追いやすい
・コンポーネントの再利用性が高まり、全体的なデザインに統一性が生まれる
簡単にそれぞれ解説します。
コンポーネント設計の指針にできる
VueやReactで書いていて悩むことの一つに、コンポーネントを分割する粒度があります。これはコンポーネントとして切り分けるべきか否か...絶対的な正解はないのでどうしても悩むポイントだと思います。
ただ、Atomic Designを指針にすれば、コンポーネント分割の方針は簡単に定まります。機械的にこれはAtom、あれはMoleculeと判定し、切り分けていけばいいので(といっても、そんな簡単な話ではありませんが...)。
とくにチーム開発において、コンポーネントの分割粒度がバラバラだと後で苦労することになるので、その点を統一しやすいのは大きなメリットだと感じました。
データの流れを追いやすい
VueやReactでは親コンポーネントから子コンポーネントにデータを渡す場合、propsを使います。ただ、あんまり適当にコンポーネントを切り分けていると、propsやemitが各コンポーネントファイルに乱雑に記述され、データの流れを追うのに四苦八苦する未来が待っています。
Atomic Designでコンポーネントを切り分ける場合、基本的には子コンポーネントの機能、ふるまいは親コンポーネントから注入されるように設計します。propsでどんどん子コンポーネントにデータを流していくイメージですね。こうすれば、データの流れは親から子に向かっての一方向性になり、データの流れをつかみやすくなります。(いわゆるFluxアーキテクチャに近い感じかもしれない)
とはいえ、Pagesのみをステートフルにしておき、それ以下のコンポーネントは全てステートレス、というのは実質的にほぼ不可能なので、organismくらいまではステートフルな設計を許すというのが現実的だと思います。
molecule以下が自身でデータを持ち出すと再利用性が一気に失われるので、避けるのが無難です。例としてはこんな感じ。
<template>
<h2>あかしぃのブログ</h2>
</template>
<template>
<h2><slot /></h2>
</template>
上のコンポーネントは再利用できる箇所が非常に限られますが、下の設計ならばあらゆる場所で使いまわせそうですね。
コンポーネントの再利用性が高まり、全体的なデザインに統一性が生まれる
前もってデザイナーと認識合わせをしておく必要がありますが、同じコンポーネントを使いまわせるようにしておけば必然的にデザインに統一性が生まれやすくなります。
他にも、同じようなパーツなのにコーディングした人によってコンポーネントの書き方が全然違う!ということも減り、保守性も高まります。
また、コンポーネント管理ライブラリの「storybook」を使えば、既存のコンポーネントの見た目、機能を簡単に可視化できるので、デザイナーとの認識合わせや、後からチームに入ってきた人のキャッチアップがスムーズになるといったメリットもあります。
Atomic Designでコンポーネント設計をするデメリット
次に、Atomic Designでコンポーネント設計をして感じたデメリットについて書いていきます。こちらも大きく分けると以下の2つ。
・チームで認識を統一していないと分割粒度がバラバラになる
・ファイル数、記述が多くなり、処理も複雑になりがち
それぞれ簡単に解説します。
チームで認識を統一していないと分割粒度がバラバラになる
Atomic Designによるコンポーネント分割には絶対的な正解はないようで、どうしても個人の主観が入ってきます。
Atomであれば単純明快なのですが、とくにOrganism、Templateあたりは書く人によって基準がブレることが多く、「うーん、これはどちらかというとTemplateなのでは...?」みたいなケースも何度かありました。
まぁ、OrganismとTemplateの境目は曖昧な感じがしますし、これは絶対にTemplateじゃないとダメ!なんてことはほぼないので、あまり厳密に考える必要もない気がします、
大事なのは、どこまでAtomic Designを適用するかをチーム内で共有しておくことです。ガチガチに適用するのか、それともざっくりとした感じにするのか、これが共有されていないとコンポーネント分割粒度がバラバラになり、後で大変なことになります。なんとなくで進めるのは危険ということですね。
ファイル数、記述量が多くなり、処理も複雑になりがち
ファイル数、記述量が多くなるというのは直感的に理解できるかと思います。
処理が複雑になるというのは、具体的な例を書くとこんなケースです。
<template>
<div>
<form @submit.prevent="submit">
<input v-model="text" />
<button>送信する</button>
</form>
</div>
</template>
<script>
export default {
data() {
return {
text: ''
}
},
methods: {
submit() {
// textを送信する処理
}
}
}
</script>
<template>
<div>
<form @submit.prevent="submit">
<Form :text="text" />
<button>送信する</button>
</form>
</div>
</template>
<script>
import Form from '@/components/organisms/Form'
export default {
components: {
Form
},
data() {
return {
text: ''
}
},
methods: {
submit() {
// textを送信する処理
}
}
}
</script>
下の例では、子コンポーネントとしてFormコンポーネントがあり、inputタグがあると仮定しています。inputタグのためだけにFormコンポーネントを切る、というのはあまりないと思いますが...。
上の書き方なら、submitの処理はthis.textを送信する処理だけを考えればOKですが、下の場合はそれに加えて、propsで渡したtextの更新処理を子コンポーネント側で実装する必要があります。
これくらいなら簡単なものの、コンポーネントの階層が深くなったり、関連するコンポーネントの数が多くなったりすると、どんどん処理が複雑になります。
まとめ
Atomic Designを導入してよかったかということを考えたときに、その価値はあったと感じています。やっぱりコンポーネント設計、分割の粒度がチームで統一されるのは大きなメリットです。
ただ、構成要素が少ないページなどでは、「これだけのためにいくつコンポーネントを作らなきゃならないんだ...」と感じたのもの事実なので、とりあえず導入しておけば良い、とは言えなさそうです。
ただ導入するにせよ、しないにせよ、コンポーネント分割においてAtomic Designは非常に参考になる考え方なので、ぜひ知っておくことをおすすめします。