オレオレのAtomic Designの指針です。
世間のAtomic Designと合わないところが結構あると思いますが、苦情は受け付けません。
(コンポーネント名は説明のためにクソみたいな名前になっているので、適切なものを振るようにして下さい。)
Atomic Design 以前の概念
Presentational / Container
アプリのストア(状態)とコンポーネントを紐付けているかどうかで、下記のように呼び方を変えるのが普通です。
- Presentational component
- ストアと紐付いていない
- 見た目だけのコンポーネント。容易にテスト可能。
- Container component
- ストアと紐付いている
- Container componentを直接含むコンポーネントもcontainer componentです。
なるべく前者によせてコンポーネントを作れるといいですね。
コンポーネントの外に対する空白などのスタイル(例: margin-top
とか)はなるべく下層のコンポーネントに持たせない
コンポーネントの再利用性が下がるので、コンポーネントの外側の空白などはコンポーネントに持たせないほうが得することが多いです。
あと、空白もたせる場合は、なるべく margin-bottom
よりも margin-top
に揃えるなどがあります。
Atomic Design 上の概念
Atoms
Styled componentおよびそれ相当の要素レベルぐらいの単位のコンポーネントのこと。
export const AtomicInput = styled.input`
font-size: 10pt;
`
基本的には必要に応じて抽出するぐらいなもので、これを見出すぐらいなら最初にMoleculesを見出すのが先だとおもいます。極論、無くても構わない。
ものの本を見ると画面カンペ(プロトタイピングスケッチ)からいきなり見出しているケースがあるのですが、そういうものがあるとも限らないし、発想方法がDOMに根付いておらずプログラマー向けじゃない気がするので、同列に考えなくてもいいと思います。
Molecules
自分が何らかのUIライブラリを構築するとして、その基本単位のコンポーネントのこと。
一般的なUIコンポーネントを抽出するなら、細かさを気にせずにまずmolecule扱いでいいです。Atomかどうか気にしなくていい。
export const MoleculeInput: React.FC<{value: string, onChange: (value: string) => void}> =
({value, onChange}) => {
const handler = (ev: React.ChangeEvent<HTMLInputElement>) => { onChange(ev.target.value) }
return <AtomicInput type="text" value={value} onChange={handler} />
}
Organisms
業務処理上、なんらかの意味があるひとまとまりのコンポーネント群のこと。個人的にはアプリのストア(状態)と紐付ける単位は基本的にここだと思っています。
export const SearchBox: React.FC<{ searchText: string, onSearchTextChange: (value: string) => void }> =
({searchText, onSearchTextChange}) => <form>
<MoleculeInput value={searchText} onChange={onSearchTextChange} />
</form>
ストアと紐付けるとこういう感じですね。これもorganismです。(Organismはorganism・molecules・atomsを含むことができます。)
export const SearchBoxContainer: React.FC => {
const { searchText, onSearchTextChange } = useSearchBoxAppState();
return <SearchBox searchText={searchText} onSearchTextChange={onSearchTextChange} />
}
データのローディング中表示とか、このコンポーネントレベルで発生するという前提で検討していくことになります。
Organismの大きさについて
案外思ったよりorganismは細かく分けることができるものです。
たとえば、上記の例だと「検索テキストボックス」らしきものを作っていますね。
「検索テキストボックス」は「検索結果表示」と最終的に対になるでしょうが、1つのorganismとしてこれらを実装する必要はありません。別々に分けて作りましょう。
Templates
複数のorganismsを入れることを想定した、レイアウト専用の枠コンポーネントです。
export const SeachResultTemplate: React.FC<{searchBox: React.ReactNode, searchResult: React.ReactNode}> = ({searchBox, searchResult}) =>
<div style={{ display: "flex", flexDirection: "column" }}>
<div>{ searchBox }</div>
<div style={{ marginTop: "1rem" }}>{ searchResult }</div>
</div>
配置専用という感じです。フレックスボックスが大量に出てきます。なお、これをatoms扱いしている人もいます。
Pages
Templateにorganismsを流し込んだコンポーネントです。
export const SearchPage: React.FC = () => <SearchResultTemplate
searchBox={<SearchBoxContainer />}
searchResult={<SearchResultContainer />}
/>
とはいえ 役割的にorganismsとかぶっているので、organisms扱いでもいい気はします。実際、状態によって表示が切り替わるtemplateなんかを扱うと、そういう感じになってしまいます。
世間一般でいうpagesについて
世間一般でいうpagesは、単純に「ページ」を意味することが多いです。
page 単位でアプリのストア(状態)とリンクするみたいなことがよく言われますが、基本的にパフォーマンス等の理由で無理だと思います。(イミュータブルにデータをきちんと作って、きちんとレンダリングタイミングが管理されているとかであれば可能とは思いますが)
たぶんそれしたいのは、ページ単位でデザインカンプがほしいとか、プレビュー表示みたいなことをしたい人がメインだと思ってます。
完全サーバサイドレンダリング型の従来式のMPAを作るなら、全然アリな発想ですが、SPAでは基本的にはうまくいかないと思うなあ〜
まとめ
概念的にはこんな感じです。
const Page = () => <Template
comp1={<Organism1 />}
comp2={<Organism2 />}
/>
const Organism1 = () => <div>
<Molecule />
<Molecule />
</div>
冒頭にも書きましたが、これはオレオレAtomic Design指針であって、世間のものと違うと思いますので、参考にする際は気をつけて下さい。