保守性の高い設計のために
今回業務上で、非常に保守性の高いUIをつくる必要が出てきました。
大きなシステムリプレイスをしなくても十分に拡張・改修が可能であること。(もちろん小さな改善を日々行えることが前提)
こういった設計をするときは、横槍が入っても逃げ道があることが大事だと考えています。
挿げ替えや切り離しなど、コンポーネント設計はここに大きなメリットがあります。
アーキテクチャ選定
React
まずJavaScriptというベース知識さえあれば、あとは一般的なプログラミング手法の応用で拡張がしやすく、導入実績も豊富なReactをプロジェクトにおいて採用。
実際に保守ができるかどうかを決定付けるのはプロダクトのコードだけではなく、対応する人間が順応できるかも重要になります。
幸いにもES6以降のJavaScriptであれば他言語の経験者も入りやすそうですし、jQueryから入っているデザイナーにもある程度馴染みがあります。
しかも今回はサーバーサイドにNode.jsをつかうこともあり、isomorphic観点からも「JavaScript」に寄せることに大きな意味がありました。
Atomic Design
その上で、コンポーネント構成をいかに柔軟なものにできるかが鍵でした。
そこでAtomicDesignに目星をつけてみました。
良いUIを継続的に開発していくには、デザイナーの視点は必須です。
そこで、共通言語として用いやすい思想を取り入れます。
デザイナーは全体を俯瞰したイメージを大切にしますので、1つトンマナが揃うことを重要とします。
AtomicDesignは特定の粒度で一覧性が高く、この要望に答えやすい設計と言えます。
実際コンポーネントという概念が理解できている人であれば、AtomicDesignのつくりを説明するのはそんなに難しいことではありませんでした。
特にこれまでUIに関するコーディング業務を経験している人であれば、どういった面で苦労があるのか身をしみてわかっているのか、有用性はすぐにわかってくれました。
やっぱりあった「つらみ」
ただ、「で、それ本当に成り立つの?」という空気感のほうがありました。
短所としては、やはり作るのがとてもしんどいこと。
唯でさえReactは記述量が多くなりがちなんですが、AtomicDesignを導入することでコードの量は飛躍的に増加します。
保守性を上げると分厚くなっていくのは仕方ないことですが、ちょっとしたUIが作りたいだけなのにやたらとファイルも増えていきます。
まずはあまりAtoms/Moleculesの事を考えずに一枚岩のOrganismsを組んでから、細かい部品に剥がしていくのが良いかもしれません。(特にアジャイル開発をするなら尚更)
Atomsをつくるのは超汎用的な関数をつくっていくことに近いです。
思想のとおりでいくと、このAtomsは基本的にはすべてグローバルな存在です。ありとあらゆるコンポーネントから呼び出されることが想定され、影響範囲が超大になることが予測できます。
そこでコンポーネント間のI/Oは常に意識する必要があり、最新の注意を払う必要があります。
React.PropTypesの定義やflowtypeなどのツールを用いたり、必ずテストコードを書いたりして堅牢に保つのが重要になってきます。
// Sample Component
const Foo = (props: String): ?React$Element<any> => {
return (
<div>{props.foo}</div>
);
};
Foo.propTypes = {
foo: React.PropTypes.string.isRequired
};
// Sample TestCode
describe('<Foo />', () => {
it('<Foo />', () => {
const Props = {foo: 'bar'}
const wrapper = shallow(<Foo {...Props} />);
assert(wrapper.find('div').prop('children'), Props.foo);
});
});
実際にコンポーネントとして組み合わせていく際に
昨今のCSS設計において注目すべき点の一つに、各コンポーネント間のレイアウト情報についてどのように管理するか、というものがあります。
コンポーネント設計をベースとした手法が主流になってきて、CSSのつくりもそれに寄せていく流れがあります。
ただし、出来上がったコンポーネントを画面上にどのように配置するのか(つまりpadding/margin/floatなどのレイアウト系CSS)がいまいち確立していません。
そこでAPBCSSの階層化を活用しました。
これは常に1つ上位のコンポーネント内でレイアウト情報を持つことで、AtomicDesign構造のとおりにCSSを持てるメリットがあります。CSS in JS系の思想とも合致します。
// Atoms
const styles = {
fontSize: 16,
color: '#0ff'
};
const Atoms = () => {
return <p style={styles}>WTF!</p>;
};
// Molecules
const styles = {
padding: 20
};
const Molecules = () => {
return (
<div style={styles}>
<Atoms />
</div>
);
};
TemplateのテストとStorybook
AtomicDesignの概念にTemplateというものがあります。
これは各UIに対してページを構成するためのコンテンツ情報がまだ流し込まれていない状態をさします。
ここの概念がReactのPropsとうまく結合します。
つまりTemplateというエントリーポイントに対して、Propsというコンテンツ情報を流し込むことで、実際のUIを生成することが可能になります。
この構成は非常にテスタブルで、応用性の高いものになっているはずです。
const Props = { // Contents Data
foo: 10,
bar: 'Sample Text',
baz: '//www.image.com/i/xyz.png',
bool: true
};
...
<EntryPoint {...Props} /> // Template
ReactにはStorybookというとても便利なツールがあります。
これは実際のUIコンポーネントのコードに対して、enzymeでテストコードを書くようにパターン網羅が出来ます。
表示したいパターン分だけTemplateに対してProps情報を用意するだけでOKです。
storiesOf('EntryPoint', module)
.add('Alpha', () => {
const Props = {
foo: 'Alpha'
...
};
return <EntryPoint {...Props} />;
})
.add('Beta', () => (
const Props = {
foo: 'Beta'
...
};
return <EntryPoint {...Props} />;
));