BLOG

ブログ

  • Top
  • Blog
  • Atomic DesignによるReactコンポーネントの設計手法

Atomic DesignによるReactコンポーネントの設計手法

ブログサムネイル

本記事では、Reactでのコード例をもとに、どういったポイントを押さえればAtomic Design(アトミックデザイン)を上手く運用することができ、開発効率を上げることができるかをご紹介します。

「Reactを使ったAtomic Designの構成で開発を進めたい」

「Atomic Designで開発を進めるにあたっての注意点を知りたい」

そんな悩みがあればぜひご覧ください!

Atomic Design(アトミックデザイン)とは?

アメリカのWebデザイナーBrad Frost氏が考案、提唱したフロントエンドの設計手法です。

参照:http://bradfrost.com/blog/post/atomic-web-design/

Atomic Designは自然界で発生するプロセスを適用して、ユーザーインターフェースを設計しています。

Atomic Designでは次のように、画面を構成する要素を以下の5つの階層に分類します。

Atomic Design階層

Atoms(原子)

インターフェースを構成する基本的な構成要素。

フォームやラベル、入力ボタンなどの基本的なHTML要素で構成され、それ以上に分けられないUIパーツ。

Molecules(分子)

Atomsの組み合わせで作るUI要素の比較的単純なグループ。

フォームラベル、検索フォームなど。

Organisms(生物)

MoleculesまたはAtomsまたはその他のOrganismsで構成される比較的複雑なUIコンポーネント。

Templetes(テンプレート)

コンポーネントをレイアウトに配置し、デザインの基礎となるコンテンツ構造を明確にするページレベルのオブジェクト。

Pages(ページ)

Templateに具体的なデータを流し込んだもの。

このようにコンポーネントが階層化されていることで、さまざまなメリットがあります。

それについては後述しますが、開発者が共通の設計思想に基づいて開発をおこなえるのが一番の利点だと思います。

実際にAtomic Designを使ってみて、原理原則があるため、コードレビューもやりやすいですし、設計に関しても迷うことが少なくなりました。

開発者が共通の認識を持った設計で開発をおこなえることは、開発スピードの向上やコミュニケーションコストを下げることにも繋がると思います。

Reactを使ったAtomic Design構成の紹介

ここからは、具体的な使い方を踏まえて、どうすればReactを用いてAtomic Designをうまく開発で使っていけるかを紹介します。

全体の実装例は、こちらを参考にしてみて下さい。

参考:https://github.com/diegohaz/arc/tree/master/src-example/components

Pages(ページ)

Templatesに具体的なデータを流し込む層です。

API通信など外部とのやりとりはPagesのみでおこなうようにすると、データをあちこちで取得しないで済むのでオススメです。

必要なデータの取得はこの層でおこなって、Organisms以下の層にここで取得したデータを渡してあげましょう。

import React from 'react'

import {
  PageTemplate, Header, Hero, Footer, FeatureList,
} from 'components'

const HomePage = () => {
  return (
    <PageTemplate
      header={<Header />}
      hero={<Hero />}
      footer={<Footer />}
    >
      <FeatureList />
    </PageTemplate>
  )
}

export default HomePage

Templates(テンプレート)

ページ全体のレイアウトを決める層です。

ページから渡ってきたデータを元にOrganismsやMoleculesなどを組み合わせて構築します。

全体的なレイアウトについてはWrapperコンポーネントで指定してあげるようにするのが一般的です。

import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import { size } from 'styled-theme'

const PageTemplate = ({
  header, hero, sponsor, children, footer, ...props
}) => {
  return (
    <Wrapper {...props}>
      <Header>{header}</Header>
      {hero && <Hero>{hero}</Hero>}
      {sponsor && <Sponsor>{sponsor}</Sponsor>}
      <Content>{children}</Content>
      <Footer>{footer}</Footer>
    </Wrapper>
  )
}

PageTemplate.propTypes = {
  header: PropTypes.node.isRequired,
  hero: PropTypes.node,
  sponsor: PropTypes.node,
  footer: PropTypes.node.isRequired,
  children: PropTypes.any.isRequired,
}

const Wrapper = styled.div`
  display: flex;
  flex-direction: column;
  padding-top: 3.75rem;
  min-height: 100vh;
  box-sizing: border-box;
  @media screen and (max-width: 640px) {
    padding-top: 3.25rem;
  }
`

const Header = styled.header`
  position: fixed;
  top: 0;
  width: 100%;
  z-index: 999;
`

const Hero = styled.section``

const Sponsor = styled.section``
const Content = styled.section`
  width: 100%;
  box-sizing: border-box;
  margin: 2rem auto;
  max-width: ${size('maxWidth')};
`

const Footer = styled.footer`
  margin-top: auto;
`
export default PageTemplate

Organisms(生物)

Atoms、Moleculesもしくは他のOrganismsから成るコンポーネントです。

よくOrganismsかMoleculesどちらの粒度になるか迷うケースがありますが、

複数のMoleculesを組み合わせているあるいはドメイン知識(特定の領域に基づく知識)が入っているかどうかで判断すると分かりやすいです。

OrganismsではAtomsも使えますが、どうしても使わないといけない場合を除き、原則使わないようにするとスッキリしたコードになります。

OrganismsがFatになりがちな課題があるので、その点については後述します。

import React from 'react'
import styled from 'styled-components'
import {
  Feature, Link, Heading, Paragraph,
} from 'components'

const FeatureList = ({ ...props }) => (
  <div {...props}>
    <StyledHeading>Basic stack</StyledHeading>
    <Description>
      It includes everything necessary to build a typical web app with focus on productivity and developer experience.
      <br />
      <Link href="https://github.com/diegohaz/arc/wiki/Workflow">Learn more about the recommended workflow</Link>
    </Description>
    <Grid>
      <StyledFeature
        icon="react"
        link="https://facebook.github.io/react"
        title="React"
        code="<MyComponent attr='value' />"
      >
        The Facebook's JavaScript library for building user interfaces using components.
      </StyledFeature>
      <StyledFeature
        icon="react-router"
        link="https://github.com/ReactTraining/react-router"
        title="React Router"
        code="<Route path='/sample-page' />"
      >
        The most popular declarative routing library for React and React Native.
      </StyledFeature>
      <StyledFeature
        icon="webpack"
        link="https://webpack.github.io/"
        title="Webpack"
        code="npm run build"
      >
        The awesome module bundler with
        {' '}
        <Link href="https://webpack.github.io/docs/hot-module-replacement.html">Hot Module Replacement</Link>
        {' '}
        enabled.
      </StyledFeature>
      <StyledFeature
        icon="jest"
        link="https://facebook.github.io/jest"
        title="Jest"
        code="npm run test"
      >
        The great testing framework used by Facebook to test all their Javascript code.
      </StyledFeature>
    </Grid>
    <StyledHeading>Optional features</StyledHeading>
    <Description>
      Features separated into another branches so you can use them only if you need to.
    </Description>
    <Grid>
      <StyledFeature
        icon="redux"
        link="https://github.com/diegohaz/arc/tree/redux"
        title="Redux"
        code="git clone -b redux https://github.com/diegohaz/arc my-app"
      >
        The predictable state container for JavaScript apps.
      </StyledFeature>
      <StyledFeature
        icon="dist"
        link="https://github.com/diegohaz/arc/tree/redux-ssr"
        title="Server Side Rendering"
        code="git clone -b redux-ssr https://github.com/diegohaz/arc my-app"
      >
        Write once and run on both server and client.
      </StyledFeature>
    </Grid>
  </div>
)

const Grid = styled.div`
  display: flex;
  flex-flow: row wrap;
  > * {
    width: calc(50% - 2rem);
    @media screen and (max-width: 640px) {
      width: 100%;
    }
  }
`

const StyledHeading = styled(Heading)`
  text-align: center;
`

const Description = styled(Paragraph)`
  text-align: center;
  margin: 2rem;
  @media screen and (max-width: 640px) {
    margin: 1rem;
  }
`

const StyledFeature = styled(Feature)`
  margin: 1rem;
  @media screen and (max-width: 640px) {
    margin: 0;
  }
`

export default FeatureList

Molecules(分子)

必ずAtomsの組み合わせでできている状態にします。

他のMoleculesやOrganismsを使ったMoleculesを作らないようにしましょう。

Moleculesに関してはドメイン知識が入っていないかを注意して作成していくようにすると汎用的に使えるコンポーネントができます。

import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import { ifProp } from 'styled-tools'
import {
  Icon, Link, Paragraph, Heading, Badge, PreformattedText,
} from 'components'

const Feature = ({
  icon, title, link, code, children, ...props
}) => {
  const { soon } = props
  return (
    <Wrapper {...props}>
      {icon && <StyledIcon icon={icon} width={64} />}
      <Text>
        <Heading level={2}>
          {link ? <Link href={link}>{title}</Link> : title}
        </Heading>
        <Paragraph>{children}</Paragraph>
        {code && <PreformattedText block>{code}</PreformattedText>}
      </Text>
      {soon && <StyledBadge palette="grayscale">soon</StyledBadge>}
    </Wrapper>
  )
}

Feature.propTypes = {
  title: PropTypes.string.isRequired,
  icon: PropTypes.string,
  link: PropTypes.string,
  soon: PropTypes.bool,
  children: PropTypes.any,
  code: PropTypes.node,
}

const Wrapper = styled.div`
  position: relative;
  display: flex;
  padding: 1rem;
  box-sizing: border-box;
  opacity: ${ifProp('soon', 0.4, 1)};
  @media screen and (max-width: 640px) {
    padding: 0.5rem;
  }
`

const StyledIcon = styled(Icon)`
  flex: none;
  @media screen and (max-width: 640px) {
    width: 32px;
  }
`

const Text = styled.div`
  margin-left: 1rem;
  overflow: auto;
  > :first-child {
    margin: 0;
  }
`

const StyledBadge = styled(Badge)`
  position: absolute;
  top: 1rem;
  right: 1rem;
  @media screen and (max-width: 640px) {
    top: 0.5rem;
    right: 0.5rem;
  }
`

export default Feature

Atoms(原子)

これ以上分割できないUIコンポーネントの最小単位であるので、他のAtomsを利用しないように注意してください。

import PropTypes from 'prop-types'
import styled from 'styled-components'
import { font, palette } from 'styled-theme'

const Badge = styled.span`
  font-family: ${font('primary')};
  font-size: 0.75rem;
  line-height: 1.5em;
  padding: 0.1em 0.3em;
  color: ${palette('grayscale', 0, true)};
  background-color: ${palette(1)};
  border-radius: 0.16667em;
`

Badge.propTypes = {
  palette: PropTypes.string,
  reverse: PropTypes.bool,
}

Badge.defaultProps = {
  palette: 'primary',
}

export default Badge

 

MoleculesとOrganismsの使い分け

よくある悩みどころとして、MoleculesとOrganismsの使い分けが難しい問題があります。

解決策としては上述した通り、ドメイン知識が入るか入らないかで区別するのが一番分かりやすいと思います。

ドメイン知識が入っていなければMolecules、ドメイン知識が入っていればOrganismsにするとまとまりがよくなります。

ただこの基準で分けていくと、OrganismsがFatになる問題があります。

これについては、Organismsで大きなまとまりを一度作ってみても良いでしょう。

作成した後にリファクタリングをすることで、汎用的に使えるコンポーネントはMolecules切りに分けたりすると良いです。

さらに、一つのOrganismsを責務ごとに複数のOrganismsに切り分けたりすることで、いろんな責務を持った一つのOrganismsを作らないように注意しましょう。

Atomic Designでのスタイル

Atomic Designを使う上でもう一つ大事なところが、スタイルの当て方です。

どのスタイルの当て方が良いか明確に決まっているわけではないですが、styled-componentsを使うのがオススメです。

styled-componentsはAtomic Designと設計思想が似ているため、親和性が高いと言われています。

Atomic Designの課題

AtomicDesignは便利な反面、課題に感じることもあります。

実際に運用していて感じたのは、最初の段階でルールをしっかり作って、それに基づいていくことが必要なこと。

そしてそのルールに乗っ取っているかチェックできる体制がチームに整っているかが重要だと思います。

例えばReactだと、reduxやcontextを使う際にはOrganismsからしかデータアクセスできないようにする。

material-uiを使う場合はmaterial-uiのimportはatomsでしかおこなえないようにするなど、扱うライブラリなどによっても独自でルールを決めておいた方が良いです。

決めたルールを守るために、コーディング規約を作り込んでおくことと規約に基づいたチェックをしっかりおこなえる体制を作っておくことが重要です。

終わりに

Atomic Designはコンポーネントを再利用して、開発効率を向上できる便利な設計手法です。

しかし、上手く運用しないと逆に開発効率が落ちて失敗したということにもなりかねません。

プロダクトに取り入れる場合は、ルールを守って運用していけばAtomic Designがもつ効果を最大限に感じることができるようになります。

クランチタイマーでは設計の部分から開発をおこなえるので、プロダクトのコード設計でお困りの方はぜひご相談下さい。

筆者

加藤 潤一

Laravel/PHP/Goでのサーバーサイド開発やスマホアプリの開発を行っております。 お客様のプロダクトの成果を最大化できるように、要件定義から一連の開発プロセスまでサポートさせて頂きます。

  • Follow me:

SHARE:

最新情報を確認する

CONTACT

お気軽にお問い合わせください。

TEL082-236-1186

NEWSLETTER

代表の佐々⽊が⽉に1回お届けするメールマガジン。
国内外スタートアップの最新情報や最新技術のサマリー、クランチタイマーの開発事例紹介など、ITに関する役⽴つ情報を中⼼にお送りします!