Scoped CSS

使用

<template>
  <div class="hello">
    <div class="world"></div>
  </div>
</template>

<style lang="less" scoped>
.hello {
  color: #333;
  > .world {
    color: #444;
  }
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

转换结果:

<div data-v-469af010 class="hello">
  <div data-v-469af010 class="world"></div>
</div>
1
2
3
.hello[data-v-469af010] {
  color: #333;
}
.hello > .world[data-v-469af010] {
  color: #444;
}
1
2
3
4
5
6

渲染规则

  • 给组件内的(不包括子组件)所有 DOM 节点添加唯一的data-v属性(形如data-v-24167b06a)
    • 子组件的根节点会添加属于父组件的data-v属性,但根节点之下的 DOM 不会添加输入父组件的data-v属性
  • 在每条(编译后生成的)CSS 规则的最后一个选择器之后,再添加一data-v属性选择器(如[data-v-24167b06a]),以便这条规则仅对该组件内拥有该data-v属性的 DOM 节点生效

深度作用选择器

问题的产生

若是父组件的样式是scoped,如何在父组件里,覆盖子组件里 DOM 元素的样式呢?

<template>
  <div class="child-component">
    <button>你好</button>
  </div>
</template>

<script>
export default {
  name: 'ChildComponent'
}
</script>

<style lang="less">
button {
    background-color: red;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

子组件ChildComponent如上所示,button的背景颜色是red

<template>
  <div class="hello">
    <div class="world"></div>
    <ChildComponent></ChildComponent>
  </div>
</template>

<script>
import ChildComponent from './ChildComponent'
export default {
  name: 'HelloWorld',
  components: {
    ChildComponent
  }
}
</script>

<style lang="less" scoped>
.hello {
  color: #333;
  > .world {
    color: #444;
  }
  button {
    background-color: green;
  }
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

父组件HelloWorld如上所示,正如你所见,在父组件的样式里,想修改子组件ChildComponentbutton元素的背景色为green。但是因为父组件HelloWorld的样式里添加了scoped,结果并不能如愿,button 的颜色仍然是red

最终渲染出来的 HTML 和 CSS 如下所示。

<div data-v-469af010 class="hello"">
  <div data-v-469af010 class="world"></div>
  <div data-v-469af010 class="child-component">
    <button>你好</button>
  </div>
</div>
1
2
3
4
5
6
<!-- 子组件样式 -->
button {
  background-color: red;
}

<!-- 父组件样式 -->
.hello[data-v-469af010] {
  color: #333;
}
.hello > .world[data-v-469af010] {
  color: #444;
}
.hello button[data-v-469af010] {
  background-color: green;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

最终编译出的 CSS 里,关于button的规则里,button选择器后追加了属性选择器[data-v-469af010]。但是在渲染出的 HTML 里,button元素并没有data-v-469af010属性,导致父组件样式里声明的背景颜色green实际上并没有覆盖子组件里button的背景颜色red

这是什么原因呢?

正如之前渲染规则里所说,若组件存在子组件,则仅有子组件的根节点会有data-v属性,子组件根节点之下的 DOM 节点不会有data-v组件(前提是子组件的 CSS 是非 scoped 的)。此时,如果想覆盖子组件内 DOM 元素的样式,将变得较为困难。因为在父组件 CSS 的每条规则的最后,都会添加data-v属性选择器,而子组件内的 DOM 节点是没有父组件的data-v属性的,最终导致在父组件里无法覆盖子组件里 DOM 元素的样式。

解决方案

针对以上的问题,vue-loader给出了解决方案,就是使用深度作用选择器。

<style lang="less" scoped>
.hello {
  color: #333;
  > .world {
    color: #444;
  }
  /* button 元素选择器前添加 /deep/ 深度作用选择器 */
  /deep/ button {
    background-color: green;
  }
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12

父组件里在button元素选择器前添加/deep/深度作用选择器,最终生产的 CSS 如下所示。

<!-- 子组件样式 -->
button {
  background-color: red;
}

<!-- 父组件样式 -->
.hello[data-v-469af010] {
  color: #333;
}
.hello > .world[data-v-469af010] {
  color: #444;
}
.hello[data-v-469af010] button {
  background-color: green;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

加上深度选择器之后,data-v属性选择器将加在声明时的/deep/选择器之前的选择器上(此例里是加在.hello选择器上),而不是最后一个选择器上(此例里是button选择器),父元素最终覆盖了子组件里 DOM 元素的样式。

注意:

  • /deep/这种写法是用在预处理器里的,常规的 CSS 里,可以使用>>>来替换掉/deep/

其他情况

上述示例是基于父组件有scoped、子组件无scoped来阐述的,我们顺便将另外几种情况也说明一下。

父子组件都有scoped

<div data-v-469af010 class="hello"">
  <div data-v-469af010 class="world"></div>
  <div data-v-71c74cf1 data-v-469af010 class="child-component">
    <button data-v-71c74cf1>你好</button>
  </div>
</div>
1
2
3
4
5
6

父子组件都使用scoped,渲染出的 HTML 如上所示。

<style lang="less" scoped>
.hello {
  color: #333;
  > .world {
    color: #444;
  }
  /* button 元素选择器前添加 /deep/ 深度作用选择器 */
  /deep/ button {
    background-color: green;
  }
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12

我们依然在父组件里使用深度选择器,最终产出的 CSS 如下所示。

<!-- 子组件样式 -->
button[data-v-71c74cf1] {
  background-color: red;
}

<!-- 父组件样式 -->
.hello[data-v-469af010] {
  color: #333;
}
.hello > .world[data-v-469af010] {
  color: #444;
}
.hello[data-v-469af010] button {
  background-color: green;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

可以看到,尽管父子组件都添加了scoped,但是使用深度选择器之后,父组件里的选择器.hello[data-v-469af010] button的优先级高于子组件里的选择器button[data-v-71c74cf1],最终父组件成功改写子组件的样式。

父子组件都无scoped

这种情况比较简单,无需使用深度选择器,只需要加大在父组件里选择器的优先级即可覆盖子组件里的样式。

父组件无scoped,子组件有scoped

最佳实践

  • 通用组件/基础组件,不要添加scoped
    • 方便调用方覆盖样式
  • 业务组件,可以添加scoped
    • 防止被全局样式覆盖
    • 防止组件内的样式影响子组件的样式

参考: