注册
web

为什么不建议在 Vue <style> 中使用 scoped?

前言

标签下编写样式。不知你是否留意,在 标签下有一个属性经常出现 - scoped。你知道它起到什么作用吗?原理是怎样的?有没有什么弊端呢?今天我们就来聊聊它。


1. 什么是 scoped


scoped 顾名思义,与作用域有关,因为是设计组件样式的,所以可以叫他 css 作用域样式作用域。当 标签带有 scoped 属性时, 内的样式只会影响当前组件内的元素。如果你对 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 组件, 只需要在 便签上添加 scoped 属性,就能达到 限制样式作用域 的目的。


2. scoped 的作用是什么?


2.1 限制样式作用域


保证 标签内的样式仅在当前组件生效,而不会影响其他组件的样式,包括子组件。


我们来强化一下上面的代码:



<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>

运行效果如下:


image.png


然后我们在 Home.vue 中设置 Child.vue 组件的 child-inner 样式类,将文字颜色设为红色:



<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>

运行效果如下:


image.png


3. scoped 原理是什么?


scoped 的原理主要是 通过为当前组件的模板添加一个独一无二的属性,然后在 CSS 选择器中添加这个属性,从而实现样式的局部作用域


它的实现可以分为几个步骤:



  1. 当 Vue 编译器编译一个包含 scoped 标签时,会为每个 CSS 规则添加一个独特的属性,比如 data-v-4533200f。这个属性是自动生成的,确保了在整个应用中的唯一性。

image.png



  1. 编译器将模板中的每一个 HTML 标签都添加上相同的属性。这样,当浏览器解析和应用 CSS 样式时,只有带有这个属性的元素才会被这些规则影响,实现了样式的局部作用域。

image.png



  1. 对于子组件,它们的根元素也会被添加上父组件的属性,所以父组件的 scoped 样式可以影响到子组件的根节点。然而,这个属性不会被添加到子组件的内部元素,所以父组件的样式不会影响到子组件的内部样式。

image.png


这种实现方法主要利用了 **CSS 选择器的属性选择器和浏览器的样式解析机制。


4. 为什么我不建议使用 scoped


4.1. 样式优先级问题


由于 scoped 通过添加唯一的属性来工作,这会增加选择器的特异性,可能导致由于特异性不同而出现样式优先级问题。


例如父组件 Home.vue 中有一个 warning 类,表示警告信息。子组件 Child.vue 同样有包含 warning 类的警告信息,想要通过在父组件中设置 warning 类的样式,统一控制父子组件的警告样式。


    
<template>
<div class="home">
<div class="warning">parentdiv>
<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>

    
<template>
<div class="child">
<div class="warning">childdiv>
<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。亲爱的小伙伴,你怎么看呢,欢迎评论区留言讨论。


感谢阅读,愿 你我共同进步,谢谢!!!


作者:前端嘟老板
来源:juejin.cn/post/7360575576417894439

亲爱的小伙伴,你好!我是 嘟老板。我们使用 Vue 开发页面时,经常需要在 

0 个评论

要回复文章请先登录注册