回想起几年前初入职场时,面对面试官的提问“Vue中Scoped的原理是什么?”时,我的回答虽然勉强过关,但内心却充满了不确定。那时,我对知识的理解还停留在表面,只能依靠死记硬背。如今,经过几年的开发经验积累,再次审视这个问题,我有了更深入的理解。
在前端开发中,CSS模块化是一个重要的话题。常见的CSS模块化方案包括:
.block__element--modifier
的命名方式,实现样式隔离和模块化。而在Vue中,通过设置单文件组件中的style
标签的scoped
属性,也能轻松实现样式隔离,并且支持less、sass等预处理器,甚至深度集成了CSS Modules。本文将重点介绍scoped
。
在Vue单文件组件中,通过在style
标签上添加scoped
属性,可以轻松实现样式隔离。例如:
<style scoped>
.container {
background: red;
}
style>
编译后,最终的结果会在选择器上增加一个唯一的属性(如data-v-mlxsojjm
),从而实现了样式隔离:
<style scoped>
.container[data-v-mlxsojjm] {
background: red;
}
style>
Vue提供了@vue/compiler-sfc
包来解析单文件组件。以下是一个简单的示例,展示如何使用该包解析带有scoped
属性的CSS:
const { compileStyle } = require("@vue/compiler-sfc");
const css = `
.container {
width: 100px;
height: 100px;
background-color: red;
}
`;
const { code } = compileStyle({
source: css, // css源代码
scoped: true, // 是否要启用scoped
id: `data-v-${Math.random().toString(36).substring(2, 10)}`, // scoped的id
});
console.log(code);
编译结果如下:
.container[data-v-mlxsojjm] {
width: 100px;
height: 100px;
background-color: red;
}
可以看到,带有scoped
属性的style
标签中的CSS,编译后会被加上一个属性选择器,名字以data-v
开头,后面跟的是一个字符串。这个字符串其实可以自己定义,只要保证全局唯一即可。
Vue官网指出:“使用scoped
后,父组件的样式将不会渗透到子组件中。不过,子组件的根节点会同时被父组件的作用域样式和子组件的作用域样式影响。”
例如,定义一个父组件parent.vue
和子组件child.vue
:
<template>
<div class="child-container">
child
div>
template>
<style scoped>
.child-container {
color: red;
}
style>
<template>
<div class="container">
<Child />
div>
template>
<script>
import Child from "./Child.vue";
export default {
name: "Parent",
components: {
Child,
},
};
script>
<style scoped>
.container {
width: 100px;
height: 100px;
background-color: red;
}
.child-container {
color: blue !important;
}
style>
最终渲染出来的子组件里面显示的字体颜色是蓝色。这是因为子组件的根节点上既有自己声明scoped
后的属性选择器,又有父级的声明scoped
后的属性选择器,所以在父组件中就可以修改子组件根节点的样式。
为了避免这种情况,定义组件根节点的类名时,应尽量独一无二,以免被父组件的同名类名样式污染。
提供插槽的组件称为Child
,使用插槽的组件称为Parent
。slot
中的内容最终编译出来会同时含有Parent
和Child
的scopedId
,所以会同时受Parent
、Child
两个组件的样式影响。
例如:
<template>
<div class="container">
<Child>
<div class="c1">cdiv>
Child>
div>
template>
<script>
import Child from "./Child.vue";
export default {
name: "Parent",
components: {
Child,
},
};
script>
<style scoped>
style>
<template>
<div class="child-container">
child
<div>
<slot>slot>
div>
div>
template>
<style scoped>
style>
如果遇到相同权重的样式,比如在Parent
组件中写的样式是.text{ color: red }
,在Child
组件中写的样式是.text{ color: blue }
,由于子组件会先于父组件渲染完成,所以最终父组件样式会覆盖子组件相同权重的样式。
在实际开发中,常常需要在父组件中修改子组件的样式。这时,可以使用深度选择器进行样式穿透。深度选择器有以下四种语法:
>>>
/deep/
::deep{}
:deep()
例如:
.a :deep(.b) {
color: green;
}
上面的代码会被编译成:
.a[data-v-9ea40744] .b {
color: green;
}
在编译后,对应CSS样式上会带上该组件scoped
对应的属性选择器,从而能够影响子组件的样式。
本文主要介绍了Vue中scoped
的原理及其使用注意事项。通过生成一个唯一的属性选择器来实现样式隔离是scoped
的核心思想。同时,我们也需要注意子组件根节点会同时受自己和父组件样式的影响,以及scoped
在slot
中的表现。此外,深度选择器可以帮助我们在父组件中修改子组件的样式。希望这些内容能帮助大家更好地理解和使用Vue中的scoped
属性。