はじめに
フロント開発をする上で、コンポーネントの分割は必須と言ってほど必要な知識となります。
ただ、分割といっても何も考えずに分割すると、誰も手を入れられないほど複雑な構造となってしまいます。
また、無理に共通化といってコンポーネントを分割する方法もありますが、間違った DRY ( Don't repeat yourself ) となってしまうことも多々あるイメージです。
今回は CSS の設計手法である BEM を使用してコンポーネントを分割する方法を考えてみます。
BEM もそうですが、MIX という記法も必須の考え方になってくるので、そちらもセットで説明していきます。
何かの参考になればと思います。
BEM とは
BEM の説明は省略するか迷ったんですが、一応ざっくり説明します。
BEM は Blocks / Elements / Modifiers の略で、HTMLおよびCSSのクラスに名前を付ける方法となります。
-
Block
ブロックは、検索フォームやリスト表示など、1つのコンテンツを表します。
ページ上のどこでも再利用できるように独立した要素として定義されます。 -
Elements(要素)
エレメントはブロック要素に紐づく要素となります。
ブロックを構成するために用いられる要素です。 -
Modifier
モディファイアはブロックとエレメントのスタイルや状態を修飾します。
よくあるものとしては、色やサイズなどを表現するときなどに用いられます。
/* Block */
.button { }
/* Element */
.button__label { }
/* Modifier */
.button--big { }
.button__label--orange { }
実際の HTML は下記のような感じになります。
<button type="button button--big" class="button">
<span class="button__label button__label--orange">ボタン</span>
</div>
MIX
今回の BEM におけるコンポーネント分割方法では MIX という記法が必須の考え方にもなるため、こちらについても簡単に説明します。
MIX とは1つのDOMノードで異なるBEMを使用する手法となります。
例えば、ブロックコンテンツ内にある DOM がたくさん定義されることがあったりします。
<div class="card">
<figure class="card__img-wrapper">
<img
class="card__img"
src="https://xxx"
alt="card image"
width="300"
height="300"
/>
</figure>
<div class="card__body">
<div class="card__title">
<div class="card__title-text">カードタイトル</div>
<div class="card__title-link">
<a>リンク</a>
</div>
</div>
</div>
</div>
今回の例で着目する箇所は card__title
になります。
card__title
の DOM では card__title-text
と card__title-link
があります。
もし、それら以外に何かを追加したい場合などに、class の設計に悩まされることがあったりします。
そういった時に使用するのが MIX となります。
card__title-text
と card__title-link
などカードのタイトルから派生するものが複数あるとき、 card__title
の title
をブロック要素としてしまおう!というものです。
<div class="card__body">
<div class="card__title title">
<div class="title__text">カードタイトル</div>
<div class="title__link">
<a>リンク</a>
</div>
</div>
</div>
このようなテクニックにより、ブロックの中でもブロックを作ることができ、こうした設計に基づいてコンポーネントの分割を考えます。
ただ、何でもかんでも MIX を使うと、とても複雑な設計にもなってしまうため、BEM の概念も忘れないように考えながら使用するように気をつけましょう。
ちなみに
今回 MIX を使用した箇所では card__title
と title
が当てられました。
この場合、card__title
と title
にはどのようなスタイルを当てるべきでしょうか?
答えとしては、下記のような定義になります。
-
card__title
- レイアウトに関するスタイルを当てる
-
title
- コンテンツに対するスタイルを当てる
今回でいうレイアウトとは、margin / padding / position / display など、要素の配置に関係するものを指します。
あくまでもブロック要素は、そのブロックに関するスタイルを当てるだけで、配置などのレイアウトに関するものはブロックのスタイルには当ててはいけません。
こうした設計にすることによって、ブロック要素が他の箇所でも柔軟に使用できるようになります。
コンポーネントの分割
ここまでで BEM と MIX について記載しました。
あとは、コンポーネントの分割について考えるだけです。
ただ、ここまでくるのに結構時間はかかります。例えば、上記ではカードを例にしましたが、リストやナビメニューなど少し複雑な UI を作成するとなった場合、どこで MIX を使うべきかなどでとても考える時間があります。
設計に悩むことは多々ありますし、当たり前のことです。また、設計に正解なんてありません。(正解だと思ってもその UI に何か追加要素が加わるとき、追加しにくくなっていることも普通にあります)
なので、トライアルアンドエラーを繰り返して実施していきましょう。設計も毎度毎度成長させていくものです。
コンポーネント分割に関して、先ほどと同じカードを例にします。
<div class="card">
<figure class="card__img-wrapper">
<img
class="card__img"
alt="example1"
src="https://xxx"
width="500px"
height="400px"
/>
</figure>
<div class="card__body">
<div class="card__meta meta">
<div class="meta__avatar avatar">
<figure class="avatar__img-wrapper">
<img
class="avatar__img"
alt="example2"
src="https://xxx"
width="200px"
height="200px"
/>
</figure>
</div>
<div class="meta__detail detail">
<div class="detail__title">Card title</div>
<div class="detail__description">This is the description</div>
</div>
</div>
</div>
</div>
上記では、すでに MIX を使用して構成しています。
コンポーネント分割では MIX を使用したブロック要素を新たな要素として切り出す対応を実施します。
<div class="card">
<figure class="card__img-wrapper">
<img
class="card__img"
alt="example1"
src="https://xxx"
width="500px"
height="400px"
/>
</figure>
<div class="card__body">
<Meta class="card__meta" />
</div>
</div>
CardMeta
という形で別に切り出しました。(下記に切り出しのソースは記載しています)
この時、CardMeta
に当てられた card__meta
には MIX の時に説明したレイアウトに関するスタイルを当てるようにします。
<div class="meta">
<Avatar class="meta__avatar" />
<CardDetail class="meta__detail" />
</div>
CardMeta
内でも MIX が使用されていたため Avatar
/ CardDetail
の2つを切り出しています。
<div class="avatar">
<figure class="avatar__img-wrapper">
<img
class="avatar__img"
alt="example2"
src="https://xxx"
width="200px"
height="200px"
/>
</figure>
</div>
<div class="card-detail">
<div class="card-detail__title">Card title</div>
<div class="card-detail__description">This is the description</div>
</div>
以上でコンポーネント分割は完了です。
分割したことによるディレクトリ構成は下記のようになりました。
card
└── meta
├── avatar
└── detail
card に依存した UI は上記のようなディレクトリ構成で管理すると直感的になるとも思います。
ただ、 avatar に関しては、割と汎用的に使用されるとも思うので、それは card ディレクトリ内で管理しなくてもいいかな〜と思います。
まとめ
BEM におけるコンポーネント分割方法について、考えてきました。
今までコンポーネント分割においては、UI の部品単位で分けるようにやってきましたが、今回の考え方で分割すると、結構スッキリ分けられたと思っています。
また、正しく BEM で分割していくと要素ごとのスクリプトもスッキリ書けることもあったりしました。
現在フロント開発で Vue や React などコンポーネント駆動な開発をしている方に参考になっていただければと思います。