# css-scoped

为了防止样式冲突,Vue 提供了给 css 文件加上 scoped 属性的方法:

<template>
  <div class="btn"></div>
</template>
<style scoped>
  .btn {}
</style>
1
2
3
4
5
6

# 原理

编译后会给元素标签和 css 都加上 【data-v-hash】,如下:

<body>
  <div class="btn" data-v-12345678></div>
</body>
<style>
  .btn[data-v-12345678] {}
</style>
1
2
3
4
5
6

其中 hash 值是由文件路径和文件内容通过 hash-sum 生成:

// vue-loader/lib/index.js
const hash = require('hash-sum')

module.exports = function (source) {
  // ...
  const shortFilePath = rawShortFilePath.replace(/\\/g, '/') + resourceQuery

  // 下面即 hash 值
  const id = hash(
    isProduction
      ? (shortFilePath + '\n' + source.replace(/\r\n/g, '\n'))
      : shortFilePath
  )
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 子组件最外层元素

当父子组件同时设置 scoped 属性时,子组件最外层的元素既会被加上当前组件的 hash 值,也会加上父级组件的 hash 值:

// Parent.vue
<script>
  export default {
    components: {
      Child
    }
  }
</script>
<style scoped>
</style>
1
2
3
4
5
6
7
8
9
10
// Child.vue
<template>
  <div>
    <div></div>
  </div>
</template>
<style scoped>
</style>
1
2
3
4
5
6
7
8

则 Child 组件 dom 结构在被编译之后如下:

<div data-v-ParentHash data-v-ChildHash>
  <div data-v-ChildHash></div>
</div>
1
2
3

所以当子组件最外层元素设置的 class 在父组件中也存在时,则最外层样式会被父组件设置的样式影响。

# 深度作用选择器

当父组件设置 scoped 之后,如果还想要部分 class 可以覆盖子组件的样式就需要设置 '>>>' 属性:

<style scoped>
  .parent-self >>> .child-klass {}
</style>
1
2
3

编译之后会变为:

<style>
  .parent-self[hash-v-ParentHash] .child-klass {}
</style>
1
2
3

但在 sass 等预处理工具中 '>>>',会报错,可以用 '/deep/' 属性,如下:

<style scoped>
  .parent-self /deep/ .child-klass {}
</style>
1
2
3

如果在 vue-cli3 中遇到报错,也可以使用 '::v-deep' 属性:

<style scoped>
  .parent-self ::v-deep .child-klass {}
</style>
1
2
3
最后更新时间: 10/24/2021, 11:07:06 AM