Help us understand the problem. What is going on with this article?

【Vue.js】Scoped CSSのscoped具合を検証してみた

Vue.jsのScoped CSSは完全にはscopedじゃない、という話を聞いたので検証してみました。
結果、コンポーネント最上位の要素と、slotについては気をつける必要があることが分かりました。

参考記事

親コンポーネントのstyleが子コンポーネントに影響を与える仕組みは、以下が詳しいです。

検証環境

  • OS: macOS Catalina 10.15.5
  • ブラウザ: Google Chrome 83.0.4103.116(Official Build)(64bit)
  • Vue.js: 2.6.11


検証1: 素朴にstyleを当てた場合

親、子、孫コンポーネントにそれぞれ違った色を割り当てました。
すると、各コンポーネントの最上位の要素には親コンポーネントのスタイルが適用されてしまいました。
1.png

Parent.vue
<template>
  <div class="root-div">
    this is root div of parent
    <h1 class="my-title">this is h1 of parent</h1>
    <div  class="h2-container">
      <h2>this is h2 of parent</h2>
    </div>
    <Child />
  </div>
</template>

<script lang="ts">
import Vue from "vue";
import Child from "@/components/Child.vue";

export default Vue.extend({
  name: "Parent",
  components: {
    Child,
  },
});
</script>

<style scoped lang="scss">
.root-div {
  color: red;
}
.my-title {
  color: red;
}
h2 {
  color: red;
}
</style>
Child.vue
<template>
  <div class="root-div">
    this is root div of child
    <h1 class="my-title">this is h1 of child</h1>
    <div  class="h2-container">
      <h2>this is h2 of child</h2>
    </div>
    <GrandChild />
  </div>
</template>

<script lang="ts">
import Vue from "vue";
import GrandChild from "@/components/GrandChild.vue";

export default Vue.extend({
  name: "Child",
  components: {
    GrandChild,
  },
});
</script>

<style scoped lang="scss">
.root-div {
  color: blue; // こいつが効いていない
}
.my-title {
  color: blue;
}
h2 {
  color: blue;
}
</style>
GrandChild.vue
<template>
  <div class="root-div">
    this is root div of grandchild
    <h1 class="my-title">this is h1 of grandchild</h1>
    <div  class="h2-container">
      <h2>this is h2 of grandchild</h2>
    </div>
  </div>
</template>

<script lang="ts">
import Vue from "vue";

export default Vue.extend({
  name: "GrandChild",
});
</script>

<style scoped lang="scss">
.root-div {
  color: green;
}
.my-title {
  color: green;
}
h2 {
  color: green;
}
</style>


検証2: 全称セレクタ(*)を使った場合

*を使って乱暴にstyleを指定した場合どうなるかを見てみました。
結果、検証1と同じく、各コンポーネント最上位の要素だけは親コンポーネントのstyleの影響が出ました。
※検証1のコードから、Child.vueだけ変更しました。
1.png

Child.vue
<template>
  <div class="root-div">
    this is root div of child
    <h1 class="my-title">this is h1 of child</h1>
    <div  class="h2-container">
      <h2>this is h2 of child</h2>
    </div>
    <GrandChild />
  </div>
</template>

<script lang="ts">
import Vue from "vue";
import GrandChild from "@/components/GrandChild.vue";

export default Vue.extend({
  name: "Child",
  components: {
    GrandChild,
  },
});
</script>

<style scoped lang="scss">
.root-div * {
  color: blue;
}
* {
  color: blue;
}
* * {
  color: blue;
}
</style>


検証3: globalのcssがある場合

当たり前ですが、ページ全体に適用されているcssがある場合、競合します。
例えば、ページ全体に* { color: black!important }を当てたら、すべて黒になります。
1.png

App.vue
<style lang="scss">
* {
  color: black!important; // プロダクトコードで書いたら殴られるやつ
}
</style>



もちろん、!importantを付けなければ、コンポーネント内のcssが優先的に適用されます。
(この辺はScoped CSSというよりはCSSそのものの話)
1.png

App.vue
<style lang="scss">
* {
  color: black;
}
</style>


検証4: slotを使った場合

slotを使って、親から子へタグを渡した場合、渡したタグには親コンポーネントのstyleが適用されました。
1.png

Parent.vue
<template>
  <div class="root-div">
    this is root div of parent
    <h1 class="my-title">this is h1 of parent</h1>
    <div  class="h2-container">
      <h2>this is h2 of parent</h2>
    </div>
    <Child>
      <!-- 親からタグごとslotを挟むと、親のstyleが適用される -->
      <template #my-slot>
        <p>slot: this is p of parent</p>
        <div>
          <h2>slot: this is h2 of parent</h2>
        </div>
      </template>
    </Child>
  </div>
</template>

<script lang="ts">
import Vue from "vue";
import Child from "@/components/Child.vue";

export default Vue.extend({
  name: "Parent",
  components: {
    Child,
  },
});
</script>

<style scoped lang="scss">
.root-div {
  color: red;
}
.my-title {
  color: red;
}
h2 {
  color: red;
}
p {
  color: red;
}
</style>
Child.vue
<template>
  <div class="root-div">
    this is root div of child
    <h1 class="my-title">this is h1 of child</h1>
    <div  class="h2-container">
      <h2>this is h2 of child</h2>
    </div>
    <slot name="my-slot">
        <p>slot: this is p of child</p>
        <div>
          <h2>slot: this is h2 of child</h2>
        </div>
    </slot>
    <GrandChild />
  </div>
</template>

<script lang="ts">
import Vue from "vue";
import GrandChild from "@/components/GrandChild.vue";

export default Vue.extend({
  name: "Child",
  components: {
    GrandChild,
  },
});
</script>

<style scoped lang="scss">
.root-div {
  color: blue;
}
.my-title {
  color: blue;
}
h2 {
  color: blue;
}
p {
  color: blue;
}
</style>



一方、親から子へ文字列だけを渡した場合、子のstyleが適用されました。
1.png

Parent.vue
<template>
  <div class="root-div">
    this is root div of parent
    <h1 class="my-title">this is h1 of parent</h1>
    <div  class="h2-container">
      <h2>this is h2 of parent</h2>
    </div>
    <Child>
      <!-- 親から文字列だけを渡すようにすれば、親のstyleは適用されない -->
      <template #my-slot-p>slot: this is p of parent</template>
      <template #my-slot-h2>slot: this is h2 of parent</template>
    </Child>
  </div>
</template>

<script lang="ts">
import Vue from "vue";
import Child from "@/components/Child.vue";

export default Vue.extend({
  name: "Parent",
  components: {
    Child,
  },
});
</script>

<style scoped lang="scss">
.root-div {
  color: red;
}
.my-title {
  color: red;
}
h2 {
  color: red;
}
p {
  color: red;
}
</style>
Child.vue
<template>
  <div class="root-div">
    this is root div of child
    <h1 class="my-title">this is h1 of child</h1>
    <div  class="h2-container">
      <h2>this is h2 of child</h2>
    </div>
    <p><slot name="my-slot-p">slot: this is p of child</slot></p>
    <div>
      <h2><slot name="my-slot-h2">slot: this is h2 of child</slot></h2>
    </div>
    <GrandChild />
  </div>
</template>

<script lang="ts">
import Vue from "vue";
import GrandChild from "@/components/GrandChild.vue";

export default Vue.extend({
  name: "Child",
  components: {
    GrandChild,
  },
});
</script>

<style scoped lang="scss">
.root-div {
  color: blue;
}
.my-title {
  color: blue;
}
h2 {
  color: blue;
}
p {
  color: blue;
}
</style>


で、結局何に気をつければいいのか

  • コンポーネント最上位の要素になるべくstyleを当てない
  • 当てざるを得ない場合、.component-name-root { display: flex }のように、重複しないクラス名を付けて、styleを指定する
  • 自前でslotを作る場合、極力DOM構造は子コンポーネント側で定義する
  • デザインフレームワーク(vuetifyとか)の制約上slotにDOMを渡さざるを得ない場合は、予期せぬstyleが適用されないよう気をつける

所感

完全でないとはいえ、知らずに使っていてもそんなに気にならない程度にはscopedにできていると感じました。
個人的にはslotを使わないため、各コンポーネントの最上位のクラス名だけ気をつければ問題なし! という印象です。
Scoped CSS便利。

arene-calix
読みは「アレン カリックス」です(化学的にはアレーンが正しいですが、人名っぽくアレンと読んでいます)。学生時代はorganometallic chemistryをやっていました。 最近はvueやnodeなどを使ったWeb開発をやっています。 おいしいものを食べることが生きがいです。
weblio
「Weblio辞書」「Weblio英会話」の運営企業。人々の選択肢を広げる新たなサービスに挑戦中!
https://www.weblio-inc.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした