この記事は フロントエンドエンジニア Advent Calendar 2016 の10日目の記事です。
最近はずっとReact、Redux、CSS Modulesで開発していて、コンポーネントについて考えることが多かったので頭の中の考えをアウトプットしてみます。
それぞれのモジュール化
みなさん、次のようにモジュール化を行っていると思います。
HTML
- Reactなどのコンポーネント化のライブラリを使う
- 何かのテンプレートエンジンでパーシャルにする
CSS
- パーツ毎にclassを作る
- SASSのmixinにする
JavaScript
- 関数やクラスにする
JavaScriptに関してはそれほど問題ないと思います。
ただコンポーネント化に関してはCSSやJavaScriptとセットで考える必要があったり、
CSSもパーツ毎にclassにするのがいいのかmixinにするのがいいのかといった論点があります。
今回はその辺りのお話をします。
コンポーネント指向ライブラリ
Reactが多く使われているのでReactの話でいきます。
Reactはコンポーネント指向のライブラリです。
構造・見た目・振る舞いをセットにして1コンポーネントとして定義します。
すなわち、HTML、CSS、JavaScriptを3つセットで1つにします。
このコンポーネントの発想は流用が効きやすく、良いものであります。
ただ使ってみると、以下のような問題で結構悩まされます。
パターン増加問題
-
構造
は同じだが見た目・振る舞い
が違う -
見た目
は同じだが構造・振る舞い
が違う -
振る舞い
は同じだが構造・見た目
が違う -
構造・見た目
は同じだが振る舞い
が違う -
構造・振る舞い
は同じだが見た目
が違う -
見た目・振る舞い
は同じだが構造
が違う
つまり、同じようなコンポーネントでもパターンが増え、重複したコードが生まれてしまいます。
※ちなみになぜパターンが増えるのか、という疑問を抱く方もいると思います。パターンが増加する原因は、ユーザーファーストで作っていることに起因するのがほとんどです。PCとスマホでデザインを分けたり(最近だとAMPでも分けますね)、同じようなパーツでもUXをベストにしようとするとパーツごとに差異がうまれます。ユーザーファーストは、デザイナーの「違うものでもトンマナをそろえる努力」と、エンジニアの「差異をどうやって吸収するかの努力」両方の協力が不可欠です。
- Atomic Designで作ったことがあるなら、MoleculesとOrganismsの使いまわせるけど使いまわせない感に共感していただけると思います。。。
対応方法を考える
今のプロジェクトではJavaScriptは共通関数として括りだすか、またはReduxのActionとして定義しているので、幸い上記ほどパターンは増えていません。
問題になるのは以下のパターンです。
-
構造
は同じだが見た目
が違う -
見た目
は同じだが構造
が違う
対応方法は以下の3つのどれかになると思います。
- コンポーネントを構造×見た目のパターン分作る
- コンポーネントを構造ごとに作り、内部に見た目のパターンを定義し、引数でどのパターンを使うか分ける
- コンポーネントを構造ごとに作り、モジュール化した共通のCSSを読み込む、またはコンポーネントに引数でモジュールを渡す
(1)はまあ無いとして、**楽なのは(2)ですが、正しいやり方としては(3)**でしょう。
モジュール結合度の話でいうと(2)は制御結合に近く、結合度が上がるのでよくありません(modifierとして扱うか微妙な場合もあると思いますが)。
ただあまりに細かい分岐は(2)でやった方がいい時もあります。
なので、基本は(3)で作りつつ細かい調整は(2)で行う、というのがいいと思います。
対応方法を整理
要するに「コンポーネント」というのは以下のように作るのがよいです。
- HTML、CSS、JavaScript、それぞれ別でモジュール化する
- モジュール化されたHTML、CSS、JavaScriptを組み合わせて、1コンポーネントを作る
- コンポーネントはパターン毎に作るか、モジュールを引数で渡す、ただし細かい表示分けは引数で内容分岐を行う
ReactでいうとJSXに1対1対応したJSやCSSは作らず、モジュール化したJSやCSSをimportして組み合わせるか、モジュール自体を引数で渡す、ということになります。
CSSのコンポーネント指向
さて、次はCSSのコンポーネントの話です。
コンポーネント化の方法
CSSのコンポーネント化の方法は2種類あるかと思います。
1つ目は、HTMLにコンポーネント化したclassを記述します。
<button class="btn-primary-l">search</button>
2つ目は、CSSでコンポーネントを作ります。
<button class="btn-search">search</button>
.btn-search {
@include btn-primary-l;
}
どっちが良いかですが、結論をいうとHTMLコンポーネントよりCSSコンポーントの方がよいです。
以下はグリッドシステムに関する2015/11/11の記事ですが、コンポーネントにおいても同じことが言えます。
Why CSS grid systems better than HTML grid systems
https://zellwk.com/blog/migrating-from-bootstrap-to-susy/
簡単にまとめると、以下のような内容です
開発スピード
単純なサイトであればHTMLグリッドで作った方が早いが、高度なサイトの場合その限りではない。まず、マークアップでレイアウトを考える必要があり、classのつけ方でどう見た目が変わるのか把握する必要がある。
CSSグリッドで書く場合は、そのページを書くだけ。1つに集中して書くことができる。CSSグリッドは難しいと思うかもしれないが、一度基礎を整えれば、CSSグリッドの方が素早くWebサイトを構築することができる。保守性
保守性は重要。ほとんどの時間はWebサイトの維持なので、スピードより重要。
CSSグリッドを維持する方が簡単。
例えば、CSSコンポーネントであれば、コンポーネントのスタイルだけに集中することができます。
そのため、SASSでいうmodifier、mixes、wrapといった差異対応のテクニックが必要ありません。
対象のコンポーネントのCSSに書けばよいので、マークアップをシンプルに保つことができるようになります。
mixinを使うか、extendを使うか
これについてはextendの方がファイルサイズを小さくできるというメリットがあるので、どちらが良いとは言えませんが、MOCSSを作成したときにはmixinを採用しました。これはシンプルさをとっているためです。
以下の翻訳記事がよくまとまっているので参考にしてみてください。
@extendを使うべき時、@mixinを使うべき時
http://postd.cc/when-to-use-extend-when-to-use-a-mixin/
最後に
CSSに関してはまだ問題があるのですが、それに関しては CSS Advent Calender 2016 で投稿したので、そちらをご覧ください。
CSS設計の歩み、呪縛と希望
http://qiita.com/mki_skt/items/e56a15dfabd7c5dc9922
あと今年のAdventCalenderは、Reduxについてちょっとした小ネタも投稿しようかと思ってます。Redux Advent Calendar 2016 の12/21に投稿予定です。
以上です。
コンポーネントをうまく使って、快適なコンポーネントライフを過ごしましょう!