これまでSFC(Single File Component)ではBEMによる命名を使っていましたが、コンポーネントを書くうえでは冗長だと感じました。そこで、ECSSの考え方をベースとして、SFCに合わせてカスタマイズする形に落ち着きました。
SFC向けCSSということでSFCSSと呼ぶことにしています。
scopedでも命名規則は必要
まず前提として、<style scoped>
でコンポーネントのスタイルが外に漏れないとしても、命名規則は必要になります。scoped
だからといって適当に書いたクラス名のツケは、きっとあとあと払うことになるでしょう。
公式のスタイルガイドに書かれているように、サードパーティのCSSが適用されないようにするためにも、コンポーネント固有のプレフィックスなどをつける必要があります。
ただしSFCでの開発は、通常のwebサイトとは性質がことなりますので、必ずしもプレフィックスが正解ではないかと思います。
ECSSでの命名の一例
※ ECSSの考えかたなどはここでは省略します。ECSSについてはこちらのまとめが参考になります。[ECSSの概要と考え方のまとめ
]
一例になりますが、ECSSでは次のようなパターンで命名を行います。
.namespace-Component_ChildNode-variant {}
namespace
はコンポーネントが属する場所などをプリフィックスとして付与します。-variant
はBEMでいうところのModifierです。Component
がBlockでChildNode
がElementといったところでしょうか。
この命名規則をSFCに合わせて次のようにしました。
SFCSS
SFCSSでは次のようなパターンで命名を行います。
.ComponentName {}
.ComponentName_ChildNode {}
._variant {}
使うのはこの3つのパターンだけです。
ComponentName
ComponentName
はvueコンポーネントのname
と同じ名前を使います。
<template>
<div class="MyComponent"></div>
</template>
<script>
export default {
name: 'MyComponent',
}
</script>
<style lang="scss" scoped>
.MyComponent {}
</style>
ルートとなるクラス名とvueコンポーネントのname
を一致させることで、DOMノードからコンポーネントの特定が容易になります。
vue-devtoolsを使える場合にはこのような考えは不要になりますが、プロダクション環境や、vue-devtoolsがないブラウザでの検証では役に立つでしょう。
ChildNode
ChildNode
はコンポーネントのルート要素以外の、クラスを付与したい要素です。
例えば次のような粒度でChildNode
を設定します。
<template>
<div class="MyComponent">
<h1 class="MyComponent_Heading">
heading
</h1>
<ul class="MyComponent_List">
<li class="MyComponent_ListItem"
v-for="item in list"
>
{{item.label}}
</li>
</ul>
</div>
</template>
原則として次のようなChildNode
連続するようなクラス名は許容しないものとします
.MyComponent_List_Item {} /* bad */
variant
variant
は条件などによってスタイルを変えたい要素に使うクラスです。ECSSではクラス名にComponentName_ChildNode
といったセレクタをフルで含む形ですが、scoped
を利用できるので、単一のクラスとして定義します。
ComponentName
とChildNode
がパスカルケースであるのに対して、variant
はキャメルケースで記述し、クラス名の先頭を_
で始めます。
._variantClassName {}
variant
は:class
で付け外しする用途が多いので、クオートが不要な文字列で構成しておくと都合がよいです。
namespaceは不要
vueコンポーネントにおいてnamespace
は不要だと考えました。ECSSのnamespace
はコンポーネントの属しているエリアなどを示しますが、vueコンポーネントでは使われる場所を限定する必要はありません。
vueコンポーネントには一意の名前をつけることになるので、プレフィックスによる名前衝突の回避も必要ないでしょう。
ルートクラスを1つに制限する
.vue
の<style>
の中では、ルートとなるクラス(ComponentName
)は1つまでに制限します。スタイルは必ずコンポーネントと1対1になるものだけ記述します。
ルートクラスを複数書きたくなるような場合、それは別のコンポーネントに分けるサインかもしれません。
lang="scss"
を使い&
による記述の省略を行う
たとえば、先ほどのChildNode
で例示したtemplate
では、次のようにstyle
を記述します。
<template>
<div class="MyComponent">
<h1 class="MyComponent_Heading">heading</h1>
<ul class="MyComponent_List">
<li class="MyComponent_ListItem"
v-for="item in list"
:class="{_selectedItem: isSelectedItem(item)}"
>
{{item.label}}
</li>
</ul>
</div>
</template>
<style lang="scss" scoped>
.MyComponent {
&_Heading {}
&_List {}
&_ListItem {}
._selectedItem {}
}
</style>
template
でのクラス名の記述量は減りませんが、全てをフルで書く場合と比べると、style
全体の見通しがよくなると考えました。この&_
による省略は、コンポーネントを分割するときにも役に立ちます。
おわりに
このSFCSSを実案件で試したところ、コンポーネントのクラス名に悩むことが減りました。かといって適当に命名してるわけではないという、うまくバランスの取れた状態で開発を進められているように思います。