SMARTCAMP Engineer Blog

スマートキャンプ株式会社(SMARTCAMP Co., Ltd.)のエンジニアブログです。業務で取り入れた新しい技術や試行錯誤を知見として共有していきます。

Atomic DesignをVue.jsで実現するための構成と考え方 | Biscuetでの例をもとに

f:id:haguri:20190829093618p:plain

スマートキャンプのデザイナー/エンジニアのhaguriです。

弊社では8月1日、インサイドセールスに特化したCRM Biscuet(ビスケット) という新サービスをリリースしました。

biscuet.jp

Biscuetでは Vue.js + Atomic Design でコンポーネント設計をしています。今回はその構成と考え方・Biscuetチームでの運用について紹介していきます。

Atomic Design について

Atomic Design とは、コンポーネント単位で設計していくデザイン・開発手法です。

詳しくは以下の記事が分かりやすいので参考にしてみてください。

design.dena.com

簡単に説明すると、以下の画像のように、UIのパーツを5階層の単位で分割して組み立てていくものです。

f:id:haguri:20190828171341p:plain
参照:http://atomicdesign.bradfrost.com/chapter-2/

1. atoms(原子)
2. molecules(分子)
3. organisms(生体)
4. templates(テンプレート)
5. pages(ページ)

Atomic Designは明確なルールがない、UIを考えるための手法です。

実際のサービス開発に導入する場合にはこの考え方をベースにして開発チームやデザインチーム内で具体的なルールを実際に決める必要があります。

これらのルールは実際のチームによって異なりますが、この記事ではBiscuet開発チームで設定してみたルールを紹介しています。

templatesとpagesについて

Atomic Designを導入しようとすると考えなければいけないのが、templatesとpagesをどうするのかという問題です。

この2つは導入当初以下のように考えていました。

  • templates: organismsを配置するもので、pagesから送られてきたデータを各コンポーネントに流すもの
  • pages: データを取得してtemplatesにデータを渡すもの

わざわざこの2つをわけると複雑性がますと判断し、templatesを廃止しました。
いまでは廃止前に作ったものが一部残っていますが、開発している部分はtemplatesを作らずにすすめています。

Biscuetでのルール

Biscuet内では、以下のようなゆるやかなルールを設けています。

再利用性 store参照 同階層参照 プリフィックス
atoms × × b-
molecules × b-
organisms × -
pages × × -

atoms

定義: これ以上分解できない最小単位のもの

複数コンポーネントからの呼び出しを想定しているため、以下の2点に気をつけています。

  • store参照をしない
  • 他のatomsを使用しない

Biscuetでは、どのコンポーネントで使用されているのかを分かりやすくするために、プリフィックスとしてb-cardのようにb-xxxをつけています。

molecules

定義: 2つ以上のatomを使用したもの

moleculesの条件として、以下の3点だけを設定しています。

  • atomsを複数組み合わせたもの
  • 複数コンポーネントから呼び出す想定があるもの
  • store参照をしない

こちらも複数コンポーネントからの呼び出しを想定しているので、atomsと同じようにコンポーネント名に b-xxx をつけています。

organisms

定義: 各ページに特化している最小単位のもの

organismsの条件は以下のように設定しています。

  • atomsとmoleculesから構成されるもの
  • 他のページでの再利用は考えない

カードなど、同じ要素をつかって繰り返し表示するようなものはorganismsに置いています。
ただ、organismsはpagesごとにディレクトリごとに分ける構成にしているので他ページでの参照は考えません。
そのページだけで使う要素を組み合わせてorganismsにしています。

例えばカードのように1ページで複数使うようなものはorganismsにしています。

pages

定義: 各コンポーネントを配置して、ページとして成立させるもの

上記で書いたatoms, molecules, organismsを組み合わせて1つの画面を作っていきます。

ディレクトリ構成

実際のディレクトリ構成は以下のようになっています。

src /
├──App.vue
├──main.js
├──components/
│   ├── organisms/
│   │   ├── home/ 
│   │   │    └── xxxxx.vue 
│   │   ├── project/ 
│   │   └── xxxxxx/   
│   └── pages/
│       ├── Home.vue 
│       ├── Project.vue 
│       └── xxxxxx.vue 
└── plugins/
    └── biscuet-materials/
        ├── atoms/
        │   ├── BCard.vue 
        │   ├── BIcon.vue 
        │   └── xxxxxx.vue 
        ├── molecules/
        │   ├── BInput.vue 
        │   ├── BSelect.vue 
        │   └── xxxxxx.vue 
        └── index.js

App.vue

全体に関わるコンポーネントを配置しています。

<template lang="pug">
  #app
    b-toast
    b-layout
      sidebar
      router-view
    b-modal(modalName="xxx")
    b-modal(modalName="xxx")
</template>

トースターやサイドバー、全体に関わるモーダル等を配置しています。

components/

organismsとpagesをおいています。

organismsはページ内の最小単位としているので、基本的にpagesのコンポーネント名でディレクトリを切っています。

plugins/biscuet-materials/

atomsとmoleculesをおいています。

organisms以上とは違い、複数コンポーネントで読み込む可能性があるため完全に分離して以下のようにグローバルのコンポーネント登録をしています。

// index.js
const context = require.context('.', true, /.vue$/)
const components = {}

context.keys().forEach(contextKey => {
  const key = contextKey.match(/.+\/(.+)\.vue/)[1]
  components[key] = context(contextKey).default
})

export default {
  install (Vue) {
    Object.keys(components).forEach(key => {
      Vue.component(key, components[key])
    })
  }
}

これをmain.jsで以下のように読み込み、使用しています。

// main.js
import BiscuetMaterials from '@/plugins/biscuet-materials'

Vue.use(BiscuetMaterials)

さいごに

最初はガチガチにルールを決めて、各コンポーネントをどの階層に置くべきかを話し合っていました。

ただ、ルールを決めすぎると判断に迷うことが多くなってきました。現在はシンプルなルールのみを設定することで、以前より共通認識は取りやすくなってきています。

この記事で紹介した方法ははBiscuetというサービスにおいてのルールです。そのためサービスの性質によって変わってくると思いますが、ぜひ参考にしてみてください。

また、本記事の内容は弊社のデザイナーチームによるデザインブログでも紹介しています。

note.mu

デザインブログでは私がデザイナー目線での内容として書いています。こちらもよければご覧ください!

note.mu