じまろぐ

めめ

VueコンポーネントのnameからCSSのクラスを付与するディレクティブを作ってみた

VueのSFCでのCSSの命名について以前書いた。

nakajmg.hatenablog.com

このSFCSSをやってくうえでクラス名を記述するのをなんとか楽できないか考えた。

この記事はその試行錯誤の軌跡である。

はじめに結論

コンポーネントnameからクラス名を付与するディレクティブを作ってプラグインをnpmに公開したが、最終的には普通にせっせと書くのが一番ベターなのでは?というところに辿りついたというお話。

めんどくさポイント

命名規則によって命名の手間は減ったが、classをフルで記述する必要があって変わらずめんどうだった。

<template>
  <div class="MyComponent">
    <h1 class="MyComponent_Heading">
      heading
    </h1>
  </div>
</template>

<script>
export default {
  name: 'MyComponent',
}
</script>

<style lang="scss" scoped>
.MyComponent {
  &_Heading {}
}
</style>

nametemplateで参照してなんとか楽をできないかと考えた。

コンポーネントnameの参照

コンポーネントnameにつけた名前は、$options._componentTagで参照できるので、:classを使って次のように書ける

<template>
  <div :class="[$options._componentTag]">
    <h1 :class="[`${$options._componentTag}_Heading`]">
      heading
    </h1>
  </div>
</template>

が、かなりめんどうだしむしろ手間が増えてるような…🙄

methodsに定義してmixinとか…?

じゃあクラス名を返すメソッドを定義してヘルパーっぽく。mixinにすれば共通化できるし…

<template>
  <div :class="[className()]">
    <h1 :class="[className('_Heading')]">
      heading
    </h1>
  </div>
</template>

<script>
export default {
  name: 'MyComponent',
  methods: {
    className(childNode = '') {
      return `${this.$options._componentTag}${childNode}`
    }
  }
}
</script>

うーん微妙。:class="[className()]"とか毎回書くのだるそう

ならばディレクティブだ

ディレクティブにしたらまだマシになりそう

<template>
  <div v-class-name>
    <h1 v-class-name="'_Heading'">
      heading
    </h1>
  </div>
</template>

<script>
function bindClassName(el, binding, vnode) {
  const componentName = vnode.context.$options._componentTag
  const className = `${componentName}${binding.value || ''}`
  if (el.className.indexOf(className) !== -1) return
  el.className = `${className}${el.className ? ' ' + el.className :''}`
}

export default {
  name: 'MyComponent',
  directives: {
    className: {
      bind: bindClassName,
      update: bindClassName,
      inserted: bindClassName,
    }
  }
}
</script>

ディレクティブに文字列として渡さないといけなくて"'_Heading'"としないとならないのが癪に障るけど、それっぽさは出てる。

これがレンダリングされるとこうなる。

<div class="MyComponent">
  <h1 class="MyComponent_Heading">
    heading
  </h1>
</div>

vue-class-nameプラグインとしてpublish

👆のやつをプラグインにしてnpmにpublishした。

www.npmjs.com

使い方

インストール

npm i vue-class-name
import vClassName from 'v-class-name'
import Vue from 'vue'
Vue.use(vClassName)
<template>
  <div v-class-name>
    <h1 v-class-name="'_Heading'">
      heading
    </h1>
  <div>
</template>
<script>

export default {
  name: 'MyComponent',
}
</script>

:classでの動的なクラスの切り替えと親コンポーネントからのclassのマージについては動作確認済み。

おわりに

なんとかならんかと試して、最終的にプラグインとして公開してみたものの、処理の内容的にどこかしらパフォーマンスに影響でそうで微妙だから自分で使わなそう🤔

宣言的に書くのがtemplateの良いところだと思うので、めんどくさくてもCSSのクラス名は静的に書くのがベターなのではと思った。

おしまい