CSS
vue.js

Vueコンポーネントに合わせたCSSの命名規則を考えた

これまで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連続するようなクラス名は許容しないものとします :no_good:

.MyComponent_List_Item {} /* bad */

variant

variantは条件などによってスタイルを変えたい要素に使うクラスです。ECSSではクラス名にComponentName_ChildNodeといったセレクタをフルで含む形ですが、scopedを利用できるので、単一のクラスとして定義します。

ComponentNameChildNodeがパスカルケースであるのに対して、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を実案件で試したところ、コンポーネントのクラス名に悩むことが減りました。かといって適当に命名してるわけではないという、うまくバランスの取れた状態で開発を進められているように思います。