42
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

updated at

vue-loaderのScoped CSSのスタイルが子コンポーネントのルート要素に効いてしまって辛い

vue-loaderのScoped CSSには、1つ辛い点がある。それは、スタイルが子コンポーネントのルート要素に効いてしまうことである。だから、意図せず子コンポーネントのスタイルを崩してしまう危険性がある。

次のような場合、page.vueの.containerのスタイルは、page-header.vueの<div class="container">...</div>にも効いてしまう。

page.vue
<template>
  <div class="container">
    <page-header date="2017-12-07" heading="Hello!" />
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
  </div>
</template>

<script>
import PageHeader from './page-header.vue'

export default {
  components: {
    'page-header': PageHeader
  }
}
</script>

<style scoped>
.container {
  ...
}
</style>
page-header.vue
<template>
  <div class="container">
    <div class="date">{{ date }}</div>
    <h1 class="heading">{{ heading }}</h1>
  </div>
</template>

<script>
export default {
  props: {
    date: String,
    heading: String
  }
}
</script>

<style scoped>
.container {
  ...
}
.date {
  ...
}
.heading {
  ...
}
</style>

バグ?いえ、仕様です

最初、これはバグなのではないかと思ったが、どうやら仕様らしい。

With scoped, the parent component's styles will not leak into child components. However, a child component's root node will be affected by both the parent's scoped CSS and the child's scoped CSS. This is by design so that the parent can style the child root element for layout purposes.

Child Component Root Elements

回避策

回避策はいくつかある。

無名のラッパーで囲む

子コンポーネントのルート要素のみが親コンポーネントのスタイルの影響を受けるので、子コンポーネントを無名のラッパーで囲めば問題を回避できる。

page-header.vue
<template>
  <div><!-- 無名のラッパー -->
    <div class="container">
      <div class="date">{{ date }}</div>
      <h1 class="heading">{{ heading }}</h1>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    date: String,
    heading: String
  }
}
</script>

<style scoped>
.container {
  ...
}
.date {
  ...
}
.heading {
  ...
}
</style>

コンポーネントの一番外側の要素のクラス名をプロジェクト内でユニークにする

コンポーネントの一番外側の要素のクラス名をプロジェクト内でユニークにする(たとえばコンポーネント名を接頭辞として付ける)ことでも、問題を回避できる。

page-header.vue
<template>
  <div class="page-header-container">
    <div class="date">{{ date }}</div>
    <h1 class="heading">{{ heading }}</h1>
  </div>
</template>

<script>
export default {
  props: {
    date: String,
    heading: String
  }
}
</script>

<style scoped>
.page-header-container {
  ...
}
.date {
  ...
}
.heading {
  ...
}
</style>

しかし、これは命名規則で解決するということなので、前時代的な感がある。

Scoped CSSの代わりにCSS Modulesを使う

Scoped CSSの代わりにCSS Modulesを使うのが最も安全だと思われる。

page-header.vue
<template>
  <div :class="$style.container">
    <div :class="$style.date">{{ date }}</div>
    <h1 :class="$style.heading">{{ heading }}</h1>
  </div>
</template>

<script>
export default {
  props: {
    date: String,
    heading: String
  }
}
</script>

<style module>
.container {
  ...
}
.date {
  ...
}
.heading {
  ...
}
</style>

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
42
Help us understand the problem. What are the problem?