vue-loaderのScoped CSSには、1つ辛い点がある。それは、スタイルが子コンポーネントのルート要素に効いてしまうことである。だから、意図せず子コンポーネントのスタイルを崩してしまう危険性がある。
次のような場合、page.vueの.container
のスタイルは、page-header.vueの<div class="container">...</div>
にも効いてしまう。
<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>
<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
回避策
回避策はいくつかある。
無名のラッパーで囲む
子コンポーネントのルート要素のみが親コンポーネントのスタイルの影響を受けるので、子コンポーネントを無名のラッパーで囲めば問題を回避できる。
<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>
コンポーネントの一番外側の要素のクラス名をプロジェクト内でユニークにする
コンポーネントの一番外側の要素のクラス名をプロジェクト内でユニークにする(たとえばコンポーネント名を接頭辞として付ける)ことでも、問題を回避できる。
<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を使うのが最も安全だと思われる。
<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>