食べログでのAtomic Design 〜どう分類しているか編〜
食べログFE(フロントエンド)チームの金野と申します。
以前の記事で、jQuery+Backbone.jsからReact/TypeScriptへのリプレースを進めていることをご紹介しました。
リプレースした部分では、Atomic Designを採用しています。
今回の記事では、採用した理由や食べログでの分類方針についてご紹介します。
Atomic Designを導入した目的
Atomic Designを導入したねらいは以下になります。
・コンポーネントの責務がより明確になる
・見た目の粒度だけでなく、ロジックの責務も明確にできる
・「ドメインが入るか/入らないか」。「抽象的か/そうでないか」の区別が明確になる
・世間的にも浸透している概念のため、デザイナー・エンジニア間の共通言語を作れる
食べログではもともとUIコンポーネントをFLOCSSの考え方に従ってレイヤー分けしていましたが、以下の課題がありました。
従来のFLOCSSで感じた課題
・CSS設計思想の一つのため、JSと一緒に管理しづらい
・「コンテンツ(文脈)問わず汎用的に使える抽象的なモジュール」と、「特定のコンテンツ・文脈でしか使えないモジュール」の区別が付けられない
FLOCSSはFoundation/Layout/Component/Project/Utilityの5つのレイヤーで構成されますが、モジュールが増えるにつれ、ComponentとProjectの数がどんどん膨れていきました。
UIコンポーネントはスタイルガイドで一覧できるよう管理しており、食べログPCサイトのComponentは現在約70個、Projectは約40個となっています。
数が増えること自体は仕方がないですが、問題はComponentレイヤーに
「コンテンツ・文脈問わず汎用的に使える抽象的なモジュール」と、
「特定のコンテンツ・文脈でしか使えないモジュール」が混在していることでした。
例えば、どんな文言でも流し込める汎用的なh1見出しと、
こちらのような「ネット予約」という文言を固定した見出し。
どちらもComponentとして分類されていたため「本当に汎用的なモジュールがどれなのかスタイルガイドをみても探しづらい」という声がデザイナーからありました。
今思えば、「特定のコンテンツ・文脈でしか使えないモジュールはどんなにシンプルなDOM構造でもProjectにする」という独自ルールにしてしまえばこの課題は解決したかもしれません。
ですがReactでリプレースする新しいアプリケーションではJSX(HTML)+JS+CSSがセットで扱われます。
そもそもCSS設計の考えであるFLOCSSをそのまま使うのは限界があるのでは、という考えでした。
また、世間でもAtomic Designの導入事例は多くなっています。
見た目やDOM構造に基づいたレイヤー分けはもちろん、
「どの層からContextやAPIにアクセスするか」
「どの層は状態を持っていいか」
などロジックの分け方についても知見が多いためAtomic Designの方がエンジニア・デザイナーの共通概念としては便利だと考えました。
Atomic Designの各層の役割
ここからは、Atomic DesignのPages/Templates/Organisms/Molecules/Atomsをどのように分類しているかを具体例とともにご紹介します。
Pages
一般的な責務:
・ページのデータをTemplates層に流し込む
食べログでのPages:
・現在はただのラッパーに近い。
食べログでは現在、機能ごとにReactへのリプレースを行っています。
そのためPagesはページ全体ではなく、特定の機能のみが内包されているケースがほとんどです。
よって、現時点ではただTemplateをラップするだけの役割になっています。
「ただのラッパーならPagesは作らない」という選択肢もありましたが、「一旦はAtomic Designのセオリーに従ってみよう」ということで作成しています。
また、今後リプレースが進みページまるごとReact化された場合は、ページを非同期に遷移させるRoutingの処理などをPagesで担うことになるかもしれません。
【例】繁盛バナー:
例えば、食べログの店舗詳細ページの右下に表示される「現在、XX人がこのお店を見ています」というバナー。社内では「繁盛バナー」と呼んでいます。
こちらは現在React化されていますので、下の図の赤い枠線内がPageコンポーネントの範囲となっています。
Templates
一般的な責務:
・ページ全体のレイアウトを決める。
・Pagesの最上位。
・同一ページ内で1度しか使えない。
食べログでのTemplates:
・部分導入した範囲内のレイアウトを決める。
・ロジックは持たない。
【例】繁盛バナー:
こちらも右下の赤枠の部分がTemplateとなっておりPageと包括単位はほぼ一致しています。
ただ、Template内では見た目のみに関心を持ち、ロジックを持つことはないというのがPageと異なる点です。
Organisms
一般的な責務:
・サービスとして意味のある単位の塊。
・他のAtoms/Molecules/Organismsや純粋なHTMLで構成される。
・独立して成立するコンテンツを提供する。
食べログでのOrganisms:
・ドメインが入ったらOrganisms。
・他に依存するコンポーネントがなかったとしても、ドメインが入った時点でOrganismsにする。
・useContextによるContext接続可。
・その機能のためのAPIを叩くのはここ。
言葉の定義:
この記事での「ドメイン」「依存」は以下の意味で使用しています。
「ドメインが入っている」とは?
・特定のコンテンツ・コンテキストじゃないと使えない状態。
「他のコンポーネントに依存」とは?
・「他のコンポーネントがないと成り立たない」状態。
・つまり、別のコンポーネントをimportしている状態。
【例1】繁盛バナー:
こちらも右下の赤枠の部分がOrganismsとなっています。
繁盛バナーは結果的にPages/Templates/最上位のOrganismsの単位がほぼ一致しています。
【例2】ネット予約用見出し
先程FLOCSSの例でお見せした「ネット予約」という見出し。
こちらはAtomic Designで分類するとOrganismsになります。
「ネット予約に関する情報の見出し」という特定の文脈・コンテンツでしか使えないためです。
依存コンポーネントがなくても(ピュアなHTMLしかなくても)、ドメインが入っていればOrganismsとしています。
Molecules
一般的な責務:
・一つ以上のAtomsに依存したcomponent。
・ユーティリティ的な塊
食べログでのMolecules:
・抽象的な機能を提供する。
・ドメインが入ってはいけない。
・他のAtomsやMoleculesのコンポーネントに依存している。
・Contextへのアクセスはしない。
・自分自身で状態はなるべく持たない。
Moleculesも汎用的・抽象的に使えるコンポーネントのため、特定のコンテンツ・コンテキストじゃないと使えないものはNGです。
また、Moleculesから直接Contextにはアクセスせず、自分自身でもなるべく状態を持たないようにしています。
Atomsとの違いは「他のコンポーネント」に依存しているかどうかです。他のAtomsやMoleculesをimportして使う場合はMoleculesとしています。
【例】TabelogButton
食べログのサイト全体で汎用的に使うデザインのボタンはMoleculesとして作っています。
なぜAtomsではないかというと、次の項でご紹介する「見た目を持たないbuttonタグ」をAtomsとしてコンポーネント化しており、それを拡張しているためMoleculesとしています。
Atoms
一般的な責務:
・これ以上分けられない塊。
・汎用的に使えるcomponent。
食べログでのAtoms:
・抽象的な機能を提供する。
・ドメインが入ってはいけない。
・Contextへのアクセスはしない。
・自分自身で状態はなるべく持たない。
・他のコンポーネントに依存していなければAtoms。
Moleculesとほぼ同じですが、他のコンポーネントに依存していなければAtomsとしています。
【例1】モーダル:
このようなモーダルは抽象的、汎用的に使えるためAtomsとしています。
HTMLタグとしては複数の要素を組み合わせた構造となりますが、UIパーツとしては分解して使うケースはないのでAtomsとしています。
また、他のコンポーネントには依存していないのもポイントです。
bodyやフッターには別のコンポーネントが入りますが、childrenやpropsとして任意の要素を渡しているだけなので、「依存している」わけではありません。
ただし、「オーバーレイは別のAtomsとして切り出す」「✗ボタンを別のAtoms/Moleculesとして切り出す」ことになったらモーダルはMoleculesとなります。
【例2】見た目を持たないボタン:
HTMLのbuttonタグを拡張したコンポーネントです。スタイルはあてません。
MoleculesのTabelogButtonはこちらを拡張して作成しています。
基底となる最小単位のコンポーネントを作ることで、共通する機能をもたせられるようにしています。
【例3】2カラムレイアウト:
画面を2カラムに縦分割するコンポーネントです。
中に置くコンテンツは外からchildrenやpropsとして受け取るのでコンテンツに依存しない使い方ができます。
また、抽象的・汎用的に使える、且つ他のコンポーネントに依存してないのでAtomsとしています。
【例4】The Tabelog Awardのバッジ:
The Tabelog Awardを受賞した店舗につけるバッジです。
以下のようなパターンが存在します。
一見ドメインが入ってそうに見えますが、食べログというアプリケーション内ではどこでも使えるのでAtomsとしています。
運用する際の心構え
・その時必要なものだけ作ろう("You ain't gonna need it”)
・最小単位がOrganismsでもOK!
・リファクタリングは積極的に
YAGNI("You ain't gonna need it”)の原則に従い、その時必要なものだけ作るようにしています。
汎用的に使えるようなコンポーネントにしようとすると、どうしても考慮するポイントが多くなり設計・実装に時間がかかります。
その割に大して使われないとなると労力が無駄になってしまいます…。
よって、迷ったらとりあえずOrganismsとして作ってOK!としています。
別の場所でも使いたくなったり、複数のコンテキストをまたいで使われるようになったりしたら初めてリファクタリングしAtomsやMoleculesとして作り変えます。
新しいシステムにはTypeScriptによる型チェックや自動テストが導入されていますし、チーム内でも「リファクタリングしたくなったらissueを立てて積極的に行う」という雰囲気のため、リファクタリングへの心理的ハードルは非常に低くなっています。
導入してどうだったか
・各レイヤーの責務が明確になった
・Storybook上で汎用的に使えるUIを探しやすくなった
食べログではもともとFLOCSSをベースにコンポーネント志向でUIコンポーネントが作られていたため、あまり混乱なく導入できたように思います。
分類方針や心構えもチームで議論しながらブラッシュアップしていきました。
なにより、迷ったらTeams上で気軽に話し合えるチームのためスムーズに実装できています。
Atomic Designを導入したことで、各レイヤーの責務を明確に定義でき、コンポーネント内にどこまでロジックや状態をもたせるか判断しやすくなりました。
また、「特定の文脈・コンテンツでしか使えないコンポーネント」をOrganismsに押し込めたことで、Storybook上で汎用的に使えるUIを探しやすくなっています。
今回は、主に分類方針について紹介しましたが、
・実際にどのようなフローで設計・開発を行っているのか
・デザイナーとフロントエンドエンジニアの役割分担はどうしているのか
・Atomic Designを導入するにあたってどんな取り組みを行ったのか
についてはまた後日記事にしたいと思いますのでご期待ください!
最後に
食べログFEチームでは、一緒にリプレースに取り組んでくれる仲間を大募集中です!
・React/TypeScriptでバリバリ開発したい
・レガシーなシステムのリファクタリングがしたい
・アーキテクチャについて探求したい
・食べログというプロダクトに貢献したい
どれかに当てはまった方は以下のリンクを是非御覧ください!
この記事が気に入ったらサポートをしてみませんか?