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

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

More than 1 year has passed since last update.

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>
yassh
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
ユーザーは見つかりませんでした