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

前言

亲爱的小伙伴,你好!我是 嘟老板。我们使用 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>

运行效果如下:

image.png

然后我们在 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>

运行效果如下:

image.png

3. scoped 原理是什么?

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

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

  1. 当 Vue 编译器编译一个包含 scoped<style> 标签时,会为每个 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 类的样式,统一控制父子组件的警告样式。

    <!-- 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。亲爱的小伙伴,你怎么看呢,欢迎评论区留言讨论。

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

  • 24
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端嘟老板

何其有幸,得君支持,万分感谢

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值