前言
亲爱的小伙伴,你好!我是 嘟老板。我们使用 Vue
开发页面时,经常需要在 <style>
标签下编写样式。不知你是否留意,在 <style>
标签下有一个属性经常出现 - scoped
。你知道它起到什么作用吗?原理是怎样的?有没有什么弊端呢?今天我们就来聊聊它。
1. 什么是 scoped
?
scoped
顾名思义,与作用域有关,因为是设计组件样式的,所以可以叫他 css 作用域 或 样式作用域。当 <style>
标签带有 scoped
属性时,<style>
内的样式只会影响当前组件内的元素。如果你对 WebComponent
有了解的话,会发现 scoped
的作用域 Shadow DOM
比较类似。
我们先来看一段代码:
<template>
<div class="home">
parent
</div>
</template>
<script setup>
</script>
<style lang="scss" scoped>
.home {
width: 200px;
height: 200px;
background-color: lightblue;
}
</style>
这是一个很简陋的 Vue
组件, 只需要在 <style>
便签上添加 scoped
属性,就能达到 限制样式作用域 的目的。
2. scoped
的作用是什么?
2.1 限制样式作用域
保证 <style>
标签内的样式仅在当前组件生效,而不会影响其他组件的样式,包括子组件。
我们来强化一下上面的代码:
<!-- Home.vue -->
<template>
<div class="home">
parent
<Child />
</div>
</template>
<script setup>
import Child from './components/Child.vue'
</script>
<style lang="scss" scoped>
.home {
width: 200px;
height: 200px;
background-color: lightblue;
}
</style>
新增一个 Child.vue
组件,有两个 div
元素,其中根节点添加 child
样式类,内部节点添加 child-inner
样式类:
<template>
<div class="child">
child
<div class="child-inner">
child-inner
</div>
</div>
</template>
<script setup lang="ts">
</script>
<style lang="scss" scoped>
.child {
width: 100px;
height: 100px;
background-color: lightcoral;
.child-inner {
width: 50px;
height: 50px;
background-color: lightpink;
}
}
</style>
运行效果如下:
然后我们在 Home.vue
中设置 Child.vue
组件的 child-inner
样式类,将文字颜色设为红色:
<!-- Home.vue -->
<style lang="scss" scoped>
.home {
width: 200px;
height: 200px;
background-color: lightblue;
.child-inner {
color: red;
}
}
</style>
然而样式没有变化,说明 Home.vue
的样式没有渗透到 Child.vue
组件内部样式
2.2 控制子组件根节点样式
使用了 scope
的样式虽然无法影响子组件的内部样式,但是可以影响子组件的根节点。也就是说子组件的根节点同时受 父组件的作用域样式 和 子组件的作用域样式 影响。
为什么要这样设计呢?
父组件可能存在需要控制子组件布局的情况。比如,列表数据也可能需要以栅格布局展示,这就需要通过按钮触发切换不同的布局效果。
假设子组件用于列表展示,父组件提供按钮入口,允许父组件作用域控制子组件的布局,那么切换列表的展示形式就轻而易举了。
我们继续调整下 Home.vue
的样式,将 child
改为 flex
布局,内容居中:
<style lang="scss" scoped>
.home {
width: 200px;
height: 200px;
background-color: lightblue;
.child {
display: flex;
justify-content: center;
align-items: center;
}
}
</style>
运行效果如下:
3. scoped
原理是什么?
scoped
的原理主要是 通过为当前组件的模板添加一个独一无二的属性,然后在 CSS 选择器中添加这个属性,从而实现样式的局部作用域。
它的实现可以分为几个步骤:
- 当 Vue 编译器编译一个包含
scoped
的<style>
标签时,会为每个CSS
规则添加一个独特的属性,比如data-v-4533200f
。这个属性是自动生成的,确保了在整个应用中的唯一性。
- 编译器将模板中的每一个
HTML
标签都添加上相同的属性。这样,当浏览器解析和应用CSS
样式时,只有带有这个属性的元素才会被这些规则影响,实现了样式的局部作用域。
- 对于子组件,它们的根元素也会被添加上父组件的属性,所以父组件的
scoped
样式可以影响到子组件的根节点。然而,这个属性不会被添加到子组件的内部元素,所以父组件的样式不会影响到子组件的内部样式。
这种实现方法主要利用了 **CSS
选择器的属性选择器和浏览器的样式解析机制。
4. 为什么我不建议使用 scoped
?
4.1. 样式优先级问题
由于 scoped
通过添加唯一的属性来工作,这会增加选择器的特异性,可能导致由于特异性不同而出现样式优先级问题。
例如父组件 Home.vue
中有一个 warning
类,表示警告信息。子组件 Child.vue
同样有包含 warning
类的警告信息,想要通过在父组件中设置 warning
类的样式,统一控制父子组件的警告样式。
<!-- Home.vue -->
<template>
<div class="home">
<div class="warning">parent</div>
<Child />
</div>
</template>
<script setup>
import Child from './components/Child.vue'
</script>
<style lang="scss" scoped>
.home {
width: 200px;
height: 200px;
background-color: lightblue;
.warning {
background-color: lightsalmon;
}
}
</style>
<!-- Child.vue -->
<template>
<div class="child">
<div class="warning">child</div>
<div class="child-inner">
child-inner
</div>
</div>
</template>
<script setup lang="ts"></script>
<style lang="scss" scoped>
.child {
width: 100px;
height: 100px;
background-color: lightcoral;
.child-inner {
width: 50px;
height: 50px;
background-color: lightpink;
}
}
</style>
运行后会发现,Child.vue
样式应没有生效,为什么呢?
因为父组件的 warning
拼接了该元素上特有的属性,无法作用到子组件的 dom
节点。
4.2. 无法跨组件边界工作
这其实是对上一个问题的延伸,scoped
无法控制其他组件的样式,包括子组件。这在构建大型应用程序时可能会限制你的样式选项。
当然啦,针对这种场景,vue
也为我们提供了解决方案,那就是 深度选择器 - :deep()
。可以使用 :deep()
包括需要穿透的类,达到影响子元素的效果。
<style lang="scss" scoped>
.home {
width: 200px;
height: 200px;
background-color: lightblue;
:deep(.warning) {
background-color: lightsalmon;
}
}
</style>
不过若是频繁使用 :deep()
,影响代码美观和整洁度是必然的。
4.3. 性能问题
使用 scoped
可能会导致性能问题,因为浏览器在渲染时必须查找和匹配这些唯一的属性。
5. 相似方案
5.1 CSS
模块 (CSS Modules)
这是一个在编译时将类名和动画名进行本地范围限定的 CSS
文件,可以有效地实现样式隔离。
5.2 BEM
(Block Element Modifier)或者其他 CSS
命名策略
恰当的命名策略可以帮助更好地组织和理解样式设计,并实现一定程度的样式隔离。
5.3 使用 CSS-in-JS
库,如 Styled Components
或者 Emotion
这些库可以提供更强大和灵活的样式封装选项,实现完全的样式隔离。
简单列举几个可行的方案,暂时先不做详细讲解.小伙伴感兴趣的话,后续会逐步更新。
个人在项目有用过 BEM
命名策略和 CSS Module
。
BEM
结合工具函数和 scss
预处理函数,可以极大地减轻应用的心智负担,比较典型的 ElementPlus
中就有 BEM
命名策略的应用。
CSS Module
我更多的是在 React
项目中使用,Vue
项目中用的不多,需要借助插件实现。
结语
好啦,今天的内容就到这里啦。关于 scoped
这个特性,必定是 仁者见仁,各有想法。不得不说,它还是一个比较实用的特性,可以帮助我们比较方便的实现样式隔离的需求,且不需要额外的 polyfil
。亲爱的小伙伴,你怎么看呢,欢迎评论区留言讨论。
感谢阅读,愿 你我共同进步,谢谢!!!