LoginSignup
8
6

More than 1 year has passed since last update.

俺的 Atomic Design (Vue)

Last updated at Posted at 2021-10-30

変更履歴

2021/10/31 ... Templates に関する記載を変更。Pages に関する記述を変更

はじめに

フロント開発における UI デザイン手法として、Atomic Design が一番耳に入るのではと思うが、その Atomic Design を自分なりにまとめたものをメモとして記事にしようと思います。
Atomic Design については、調べるとたくさんの記事が出てくるため、Atomic Design とはなんなのかについては割愛させていただきます。
また、今回の記事では Vue / Nuxt ならではの Atomic Design を自分なりにまとめた記事となるので、 React / Angular などではあまり参考にならないかもですが、設計指針的な箇所では参考になるかもなので、是非読んでみてください〜。
また、「こうした方がもっとスッキリして簡潔だよ〜」みたいなコメントなどもどんどん募集しています!

Atomic Design に触れてみて

現在の職場では Atomic Design を採用し開発を進めているが、自分はその Atomic Design に業務で触れるのは初でした。
ただ、以下のようなレイヤー・役割があるので、それだけでもわかりやすく開発しやすそうだな〜とイメージしていました。

  • Atoms(原子)
    • これ以上分割できない最小のコンポーネント
  • Molecules(分子)
    • 複数の Atoms を組み合わせたコンポーネント
  • Organisms(有機体)
    • 1つの機能を表現するコンポーネント
  • Templates(テンプレート)
    • ワイヤーフレーム
  • Pages(ページ)
    • ワイヤーフレームに値を流し込むためのコンポーネント

この認識を元にいざコーディングとなると、細かい箇所で「ここどうするの?」みたいなのがたくさん出てきて、調べたり、聞いたりしてようやくスッキリした形となってきたので、その内容をこれから記載していく。

Atoms(原子)

Atoms は最小のコンポーネント(ボタン・ラベル・各入力項目など)となるがその他の細かい決まり事を、以下のように定義する。

  • ビジネス UI とビジネスロジックを持たない
  • 異なるプロジェクトや様々な箇所で利用できるようなコンポーネントを作成する
  • 基本的な UI としての機能しか持たない

ここの「ビジネス」とは、システムならではの要件・要望のことをさしており、システムならではの UI / ロジックを持つのではなく、本当にシンプルな UI / ロジック機能のみを持つ。
その要件を満たすことにより、次の項目(異なるプロジェクトや様々な箇所で利用できるようなコンポーネントを作成する)を実現できる。
以下のコンポーネントは、簡単なボタンではあるが、上記の項目を満たしたボタンとなる

<template>
    <button type="button" class="a-button" :class="[color, { disabled }]">
        <slot>Button</slot>
    </button>
</template>

<script lang="ts">
import { defineComponent, PropType } from 'vue';
import { ButtonColor } from '@/types/components/01-atoms/general/AButton';

export default defineComponent({
    props: {
        color: {
            type: String as PropType<ButtonColor>,
            default: () => 'default' as ButtonColor
        },
        disabled: {
            type: Boolean,
            default: () => false
        }
    }
});
</script>

<style lang="scss" scoped>
// 割愛
</style>

カラーの変更や、disabled 切り替えのロジックに関しては UI の基本的な機能となり、どのプロジェクトでも必要そうなロジックとなる。
システム要件は一切入っておらず、とてもシンプルなコンポーネントとなる。

Molecules(分子)

Molecules は複数の Atoms を組み合わせたコンポーネントとなるがその他の細かいルールを以下のように定義する。

  • ビジネスUIは持つが、ビジネスロジックは持たない
  • UIとしての機能を持つ

今回でいうビジネス UI とは、ラベル・入力ボックスの配置などを指し、システム上の要件をのんでいいと考えている。
ただ、ビジネスロジックは持たず、シンプルな UI 機能を実装する。
こちらも上記要件を満たしたコンポーネントを記載する。

<template>
    <fieldset class="m-checkbox-group">
        <template v-for="option in options" :key="option.value">
            <a-checkbox :option="option" v-model="model" @change="onChange" />
        </template>
    </fieldset>
</template>

<script lang="ts">
import { computed, defineComponent, PropType, SetupContext } from 'vue';
import ACheckbox from '@/components/01-atoms/data-entry/ACheckbox.vue';
import { ACheckboxOption, ACheckboxValues } from '@/types/components/01-atoms/data-entry/ACheckbox';

export default defineComponent({
    components: {
        ACheckbox
    },
    props: {
        options: {
            type: Array as PropType<ACheckboxOption[]>,
            default: () => []
        },
        modelValue: {
            type: Array as PropType<ACheckboxValues>,
            default: () => []
        }
    },
    setup(props, { emit }: SetupContext) {
        const methods = {
            onChange: (option: ACheckboxOption, checked: boolean) => {
                emit('change', option, checked, props.options);
            }
        };

        const computedMethods = {
            model: computed({
                get: () => props.modelValue,
                set: (newModelValue) => emit('update:modelValue', newModelValue)
            })
        };

        return {
            ...methods,
            ...computedMethods
        };
    }
});
</script>

こちらはチェックボックスグループのコンポーネントとなり、チェックボックスの配置などはこちらのコンポーネントで考慮するが、ビジネスロジックは一切持っていない作りとなっている。

Organisms(有機体)

こちらは1つの機能(ブロック)を表現するコンポーネントであるが、その他の細かいルールを下記のように定義した。

  • ビジネスUIとビジネスロジックを持つ
  • UIとしての機能はMolecules以降のレイヤーに任せる(例外もあると思う)
  • 一画面のビジネスロジックを担う
  • 表示するデータは fetch しない(Templates から受け取る)
  • その他のビジネスロジックに関わる API 連携を担う(emit しない)

ここのレイヤーはすごく悩んだ記憶があり、詳細に残せたらと思うw
まず、システムの要件・要望は全てコンポーネントに落とし込む。
ただ、ビジネスロジックでソースが膨れ上がる場合はうまく共通化を実施し、スクリプト部分を外出しする。
エレメント部分で膨れ上がる場合は、 Molecules に出すか検討し、 Molecules の要件を満たせる場合はコンポーネントとして切り出す
スタイル部分は丸ごと import するようにし、スタイルのソース( <style scoped> 以外は)は vue ファイルには記述しないようにする。(シンプルに実装できた場合は、外出ししなくてもいいと思っている)
Templates レイヤーでワイヤーフレームを表現するため、 data fetch は Organisms では実施せずビジネスロジックに関わるもののみ API 連携を実施する(データ登録・更新・削除など)
ただ、そのビジネスロジックの API 連携は上のレイヤーに emit しない(1つの機能を表現するというルールを壊すことになるため)
機能(ブロック)の単位に関しては、例えばヘッダ・フッタなどで1つのブロックとして定義する。

このようなルールをもとにコンポーネント作成を実施すると空データの場合(ワイヤーフレーム)を考慮できたり、ビジネスロジックがまとまったりと直感的にコンポーネント作成が実現できると思っている。(また新たな問題・疑問にぶつかるかもだがw)

Templates(テンプレート)

こちらはワイヤーフレームを表現するレイヤーとなるが、個人的に Vue / Nuxt ではいらないと思っている。

こちらはここまで作成してきた Organisms のコンポーネントを組み合わせるように作成する。
配置など、レイアウトを意識してコンポーネントを組み合わせる。
ルールとしては下記のように定義した。

  • API 連携はしない
  • Pages から流れてくるデータを Organisms に分配する
  • 似たような画面にも適応できるようデータは汎用的にされたデータを Organisms に流す(どちらかというと Organisms に規則な気がするがw)

Templates はシンプルにする必要があり、script など(ロジック)は記述しない。
スタイルに対し、layout に関する記述のみとする。

Pages(ページ)

Pages はそのページを表現するためのコンポーネントとなるが主に Templates にデータを流し込むだけのシンプルな定義となる。
本レイヤーのルールとしては下記のように定義した。

  • Templates に fetch data を props で渡す
  • ページに関わるものはこのレイヤーで扱う(query param など)
  • Templates コンポーネントを1つ定義する
  • ページの大枠で表現するために必要な UI ロジックを持つ

ここでの fetch data とはコンテンツを意味すると個人的には定義している。
そのコンテンツを Templates に流し込むことで、 Atomic Design のデザイン手法を維持している。
Nuxt などでは、 asyncData などを用いて fetch し、算出プロパティ(computed)でデータを props で渡すような記述になるかと思われる。
以下のサンプルは vue での Pages コンポーネントとなる。store に関しては、ちょっと適当に書いているので、そこはご了承くださいw

<template>
    <Suspense>
        <template #default>
            <o-data-list :data="data" />
        </template>
        <template #fallback>
            loading...
        </template>
    </Suspense>
</template>

<script lang="ts">
import { defineComponent, computed } from 'vue';
import ODataList from '@/components/03-organisms/ODataList.vue';
import store from './store';

export default defineComponent({
    components: {
        ODataList
    },
    async setup() {
        await store.dispatch();
        const data = computed(() => store.getters());
        return {
            data
        }
    }
});
</script>

まとめ

ここまでざっくりまとめてみたが、なんか書き足りない感じもするw
今後開発していく上で Atomic Design はまだ課題が出てくると思うが、その時はまた調査したりしていこうと思います。
新たに出た課題に対する調査結果や、コメントいただけたりした場合は、随時更新していくつもりです〜。
こちらの記事で何か参考になれば個人的にすごい嬉しく思います!

8
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
6