本記事では、Reactでのコード例をもとに、どういったポイントを押さえればAtomic Design(アトミックデザイン)を上手く運用することができ、開発効率を上げることができるかをご紹介します。
「Reactを使ったAtomic Designの構成で開発を進めたい」
「Atomic Designで開発を進めるにあたっての注意点を知りたい」
そんな悩みがあればぜひご覧ください!
アメリカのWebデザイナーBrad Frost氏が考案、提唱したフロントエンドの設計手法です。
参照:http://bradfrost.com/blog/post/atomic-web-design/
Atomic Designは自然界で発生するプロセスを適用して、ユーザーインターフェースを設計しています。
Atomic Designでは次のように、画面を構成する要素を以下の5つの階層に分類します。
インターフェースを構成する基本的な構成要素。
フォームやラベル、入力ボタンなどの基本的なHTML要素で構成され、それ以上に分けられないUIパーツ。
Atomsの組み合わせで作るUI要素の比較的単純なグループ。
フォームラベル、検索フォームなど。
MoleculesまたはAtomsまたはその他のOrganismsで構成される比較的複雑なUIコンポーネント。
コンポーネントをレイアウトに配置し、デザインの基礎となるコンテンツ構造を明確にするページレベルのオブジェクト。
Templateに具体的なデータを流し込んだもの。
このようにコンポーネントが階層化されていることで、さまざまなメリットがあります。
それについては後述しますが、開発者が共通の設計思想に基づいて開発をおこなえるのが一番の利点だと思います。
実際にAtomic Designを使ってみて、原理原則があるため、コードレビューもやりやすいですし、設計に関しても迷うことが少なくなりました。
開発者が共通の認識を持った設計で開発をおこなえることは、開発スピードの向上やコミュニケーションコストを下げることにも繋がると思います。
ここからは、具体的な使い方を踏まえて、どうすればReactを用いてAtomic Designをうまく開発で使っていけるかを紹介します。
全体の実装例は、こちらを参考にしてみて下さい。
参考:https://github.com/diegohaz/arc/tree/master/src-example/components
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
ページ全体のレイアウトを決める層です。
ページから渡ってきたデータを元に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
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
必ず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
これ以上分割できない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にするとまとまりがよくなります。
ただこの基準で分けていくと、OrganismsがFatになる問題があります。
これについては、Organismsで大きなまとまりを一度作ってみても良いでしょう。
作成した後にリファクタリングをすることで、汎用的に使えるコンポーネントはMolecules切りに分けたりすると良いです。
さらに、一つのOrganismsを責務ごとに複数のOrganismsに切り分けたりすることで、いろんな責務を持った一つのOrganismsを作らないように注意しましょう。
Atomic Designを使う上でもう一つ大事なところが、スタイルの当て方です。
どのスタイルの当て方が良いか明確に決まっているわけではないですが、styled-componentsを使うのがオススメです。
styled-componentsはAtomic Designと設計思想が似ているため、親和性が高いと言われています。
AtomicDesignは便利な反面、課題に感じることもあります。
実際に運用していて感じたのは、最初の段階でルールをしっかり作って、それに基づいていくことが必要なこと。
そしてそのルールに乗っ取っているかチェックできる体制がチームに整っているかが重要だと思います。
例えばReactだと、reduxやcontextを使う際にはOrganismsからしかデータアクセスできないようにする。
material-uiを使う場合はmaterial-uiのimportはatomsでしかおこなえないようにするなど、扱うライブラリなどによっても独自でルールを決めておいた方が良いです。
決めたルールを守るために、コーディング規約を作り込んでおくことと規約に基づいたチェックをしっかりおこなえる体制を作っておくことが重要です。
Atomic Designはコンポーネントを再利用して、開発効率を向上できる便利な設計手法です。
しかし、上手く運用しないと逆に開発効率が落ちて失敗したということにもなりかねません。
プロダクトに取り入れる場合は、ルールを守って運用していけばAtomic Designがもつ効果を最大限に感じることができるようになります。
クランチタイマーでは設計の部分から開発をおこなえるので、プロダクトのコード設計でお困りの方はぜひご相談下さい。
SHARE:
お気軽にお問い合わせください。
TEL082-299-2286
代表の佐々⽊が⽉に1回お届けするメールマガジン。
国内外スタートアップの最新情報や最新技術のサマリー、クランチタイマーの開発事例紹介など、ITに関する役⽴つ情報を中⼼にお送りします!