Gatsbyでブログを構築しながらAtomic Designを試してみました


はじめに

(日付かわったくらいに「あ、空いてる!?」と気付いた勢いで書いてます)

仕事でReact.js使うようになったので、自分のブログをGatsbyで作り直しながらAtomic Designも試してみました。
(試し始めたのが夏頃で、その後携わっているプロダクトでもAtomic Designで運用することになりました)
この記事では自分なりのコンポーネントの分け方やコードの一部を紹介していきます。

Gatsbyとは

Reactベースで作成したコンポーネントと、GraphQL経由で取得したデータをもとに静的サイトを作成するための静的サイトジェネレーターです。
チュートリアルを進めていくと、雰囲気つかめます。
サクッとブログ構築するためのスターターなどもあります。

個人的に勉強を兼ねてGatsby使い始めるのすごく良かったです。
ReactとGraphQLが一気に体験できてお得感がありました。

Atomic Designとは

Atomic DesignとはUIの設計手法の一つです。

Atoms(原子) / Molecules(分子) / Organisms(有機体) / Templates / Pages という層にわけてコンポーネントを設計します。
本を参考にしながら自分なりの各層の位置付けを決めていました。

  • Atoms: 「それ以上UIとして機能性を破壊しない最小要素」なコンポーネント
  • Molecules: 「ユーザーの具体的な動機に応える機能」なコンポーネント
  • Organisms: 「コンポーネントで完結するコンテンツを提供」するもの
  • Templates: Organisms / Molecules / Atomsコンポーネントを実際に配置して、レイアウトする
  • Pages: Templateコンポーネントに実際のコンテンツデータを流し込んだもの

「MoleculesはAtomsの組み合わせ、
 OrganismsはAtomsとMoleculesの組み合わせ、
 TemplatesはAtomsとMoleculesとOrganismsの組み合わせ、
 PagesはTemplatesに実データを流し込んだもの、という階層構造がある」
みたいな考え方がベースになっています。

(Atomic DesignはAtomic Design ~堅牢で使いやすいUIを効率良く設計するで勉強しました。
わかりやすい解説がもっとあります。。。すいません。)
(上記の本でAtomic Designの話以外にも、Container / Presenterに分ける書き方や、Storyshotsを知って実プロダクトの運用時にめっちゃ役立ちました。ありがたいです。)

少し話がずれますが、自分が仕事で携わっているプロダクトでは他のエンジニアとどうコンポーネントを分割していくか認識をあわせながら、
いくつか独自(?)ルールを設定しました。

  • AtomsでMoleculesを使うような下位層で上位層を利用することはしない(同じ階層はOK)
  • Redux storeと連携してデータを取得するのは(Reduxのアクション呼び出しも)Organisms層で実装する
  • ページ遷移時の処理(componentDidMountなど)とルーティング定義はPages層で実装する

このあたりが厳密なAtomic Designの原則だったのかちゃんと覚えていませんが、
Atoms / MoleculesはStateless Functional Componentsとして実装できるようになり、
テストやStorybookが書きやすくなる点でメリットがあると考えています。
(どこでなにを実装しているかも分かりやすくなりました)

Gatsbyのコンポーネント設計にAtomic Designを取り入れる

もとのコンポーネント

Gatsbyのチュートリアル7章でブログの1記事表示をするためのコンポーネントが紹介されています。
真っ白なページにmarkdownからレンダリングされたHTMLを表示します。
(下側のqueryはデータ取得のためのGraphQLのクエリです)

// src/templates/blog-post.js
import React from "react"
import { graphql } from "gatsby"
import Layout from "../components/layout"

export default ({ data }) => {
  const post = data.markdownRemark
  return (
    <Layout>
      <div>
        <h1>{post.frontmatter.title}</h1>
        <div dangerouslySetInnerHTML={{ __html: post.html }} />
      </div>
    </Layout>
  )
}

export const query = graphql`
  query($slug: String!) {
    markdownRemark(fields: { slug: { eq: $slug } }) {
      html
      frontmatter {
        title
      }
    }
  }
`

コンポーネント設計の概要

以下の画像は自分のブログ記事をキャプチャして各層を色分けしたものです。

  • Atoms(黒文字のところ): 「それ以上UIとして機能性を破壊しない最小要素」
    • 紫っぽい背景色を指定しているBackgroundとメインコンテンツを画面中央に表示する白背景のCenteredを用意
      • 各ページで再利用してレイアウトを統一するため
    • (図には書いていませんが HeaderでもAtomsを組み合わせています)
  • Molecules(緑枠のところ): 「ユーザーの具体的な動機に応える機能」
    • Header / Footerは読む人にとって意味がでてきたのでMoleculesに配置(とはいえAtomsでいいくらいシンプル
  • Organisms(青枠): 「コンポーネントで完結するコンテンツを提供」
    • レンダリングされた記事内容を表示するSinglePostを配置
  • Templates: Organisms / Molecules / Atomsコンポーネントを実際に配置して、正しくレイアウトする

実際のTemplates層のコンポーネントが以下のようになります。
Pages層から渡されたsiteTitleとpostを各コンポーネントに渡しつつ、
Atoms層のレイアウト用コンポーネントを呼び出しています。

// PostTemplate.js
import React from 'react';

import Background from '../atoms/_layout/Background';
import Header from '../molecules/Header';
import SinglePost from '../organisms/SinglePost';
import Centered from '../atoms/_layout/Centered';
import Footer from '../molecules/Footer';

const PostTemplate = ({ siteTitle, post }) => (
    <Background>
        <Centered>
            <Header siteTitle={siteTitle} />
            <SinglePost title={post.frontmatter.title} html={post.html} />
            <Footer />
        </Centered>
    </Background>
)

export default PostTemplate;
  • Pages: Templateコンポーネントに実際のコンテンツを流し込んだもの

Pages層では、GraphQL経由で受け取ったデータから、Templates層のコンポーネントが求めているものを取り出しています。

// PostPage.js
import React from 'react';

import PostTemplate from '../templates/Post';

export default ({ data }) => {
  const post = data.markdownRemark;
  const { title } = data.site.siteMetadata;
    return <PostTemplate siteTitle={title} post={post} />;
}

Gatsbyのチュートリアルで実装したGraphQLのクエリとセットのコンポーネントは、
Pages層のコンポーネントを呼び出すだけのシンプルなものになりました!

import React from "react"
import { graphql } from "gatsby"

import PostPage from '../components/pages/PostPage';

export default ({ data }) => {
  // Pages層のコンポーネント呼び出し
  return <PostPage data={data} />;
}

export const query = graphql`
  query($slug: String!) {
    site {
      siteMetadata {
        title
      }
    }
    markdownRemark(fields: { slug: { eq: $slug } }) {
      html
      frontmatter {
        title
      }
    }
  }
`

まとめ

Gatsbyでブログを作りながらAtomic Designを取り入れてみると、
各層で責任が分かれているのでどこになにがあるのか把握しやすくなりました。
あとはレイアウト用のコンポーネントをAtomsに配置して再利用することで、
記事一覧やタグ一覧ページの実装もデザインは流用してロジック実装だけになりました。

React.jsを使って再利用性の高いコンポーネントをふやしていくのであれば、
Atomic Designを参考にしてみるのもいいのではないかと思います。