Vue中scoped属性浅析
Scoped CSS(Vue Loader)
在vue单文件组件中,为了防止全局同css类名名样式的污染,vue-loade对单文件组件 <style>
标签增加了scoped
属性的处理。原理就是在html标签上添加data-v-xxxxxxxx
属性,然后在css类名后添加属性选择器,即利用css类选择 + 属性选择器实现样式局部化:
Parent.vue
<template>
<div class="parent">
我是来自父组件的
</div>
</template>
<style lang="scss" scoped>
.parent {
color: #333;
}
</style>
转换结果:
<template>
<div data-v-2f3286d4 class="parent">
我是来自父组件的
</div>
</template>
<style>
.parent[data-v-2f3286d4] {
color: #333;
}
</style>
使用 scoped
后,父组件的样式将不会渗透到子组件中。不过一个子组件的根节点会同时受其父组件的 scoped CSS 和子组件的 scoped CSS 的影响。这样设计是为了让父组件可以从布局的角度出发,调整其子组件根元素的样式。
我们将Parent.vue修改为:
Parent.vue
<template>
<div class="parent">
<Child></Child>
我是来自父组件的
</div>
</template>
<script>
import Child from './Child'
export default {
name: 'Parent',
components: {
Child
}
}
</script>
新增Child.vue
<template>
<div class="child">
<div>
我是子组件默认色的
</div>
<div class="red">
我是子组件红色的
</div>
<!--<div data-v-2f3286d4 class="red">-->
<!--我是子组件被父组件编译过的红色的-->
<!--</div>-->
</div>
</template>
<script>
export default {
name: 'Child'
}
</script>
<style lang="scss">
.child {
color: #999;
.red {
color: red;
}
}
</style>
下面看下如何在父组件中修改子组件中样式(两种情况):
- 父有
scoped
,子无scoped
,这种情况也是常见的各种ui中的实现,每个ui组件中无scoped,我们在父组件中可以覆盖每个ui组件的默认样式。如上边提到的父组件加scoped后,在子组件的根节点会加入data-v-2f3286d4
,我们在父组件直接这样写是没用的:
<style lang="scss" scoped>
.parent {
color: #333;
.red {
color: greenyellow;
}
}
</style>
效果:
以上直接修改的话,会被编译为:
<style lang="scss" scoped>
.parent .red[data-v-2f3286d4] {
color: greenyellow;
}
</style>
子组件中red选择的标签是没有 data-v-2f3286d4
属性的,但是我们可以在子组件中打开注释测试下:
<div data-v-2f3286d4 class="red">
我是子组件被父组件编译过的红色的
</div>
效果如下:
我们需要加 /deep/
或者 >>>
或者 ::v-deep
来修改子组件中 .red
的样式(会在 deep
使用后的class编译为 `[data-v-xxxxxxxx] .red形式),我们继续对之前打开注释的子组件进行注释,并修改父组件为:
<style lang="scss" scoped>
.parent {
color: #333;
/deep/ .red {
color: greenyellow;
}
}
</style>
上边会被编译为
.parent[data-v-2f3286d4] .red {
color: greenyellow;
}
效果:
- 父有
scoped
,子有scoped
,这种情况下大多出现在我们自己的公共组件中,这种方式并不推荐,我们写的公共组件应该不含有scoped
。参照1
中提到的穿透组件写法我们出现的结果如下:
出现这种问题的原因就是属性选择器权重 > class选择器权重,解决的方法就是需要提高父组件中覆盖样式的权重,方法很简单:加 !important
。。。
<style lang="scss" scoped>
.parent {
color: #333;
/deep/ .red {
color: greenyellow !important;
}
}
</style>
效果:
data-v-xxxxxxxx
生成规则
我们看到标签上新增属性,可能有些小伙伴会好奇data-v-xxxxxxxx中的xxxxxxxx
是如何生成的,我查阅vue loader的仓库中搜索发现:是否生产环境 ? hash(组件的内容) : hash(组件的相对路径):
const moduleId = 'data-v-' + hash(isProduction ? content : shortFilePath)****
后来有大佬发现,只根据内容有可能会是生成hash值相同,比如以下方式声明组件, issue地址:
<style lang="sass" src="./index.sass" scoped></style>
<script lang="ts" src="./index.ts"></script>
<template lang="pug" src="./index.pug"></template>
后来就改为 内容 + 相对路径
生成hash, commit地址:
const moduleId = 'data-v-' + hash(isProduction ? (shortFilePath + '\n' + content) : shortFilePath)
scoped
总结
添加了属性选择器,对于CSS选择器的权重加重了
即使加入属性选择器,但是clsss还是没变,如果全局还存在同class名称的样式,还是有可能出现覆盖,比如用在App.vue
中也定义了.red
样式:
效果:
推荐使用
CSS Modules
,我们直接生成一个唯一的class名,既保证了class的全局唯一(无法造成class名样式污染),有没有提高class选择器权重的增加
参考:
你知道style加scoped属性的用途和原理吗?
Scoped CSS
CSS Modules
New scoped ID generation since v13.4 may cause duplicated ID (needs option to disable)