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

前言

亲爱的小伙伴,你好!我是 嘟老板 。我们使用 Vue 开发页面时,经常需要在 <style> 标签下编写样式。不知你是否留意,在 <style> 标签下有一个属性经常出现 - scoped。你知道它起到什么作用吗?原理是怎样的?有没有什么弊端呢?今天我们就来聊聊它。

1. 什么是 scoped

scoped 顾名思义,与作用域有关,因为是设计组件样式的,所以可以叫他 css 作用域样式作用域 。当 <style> 标签带有 scoped 属性时,<style> 内的样式只会影响当前组件内的元素。如果你对 WebComponent 有了解的话,会发现 scoped 的作用域 Shadow DOM 比较类似。

我们先来看一段代码:

html 复制代码
<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> 标签内的样式仅在当前组件生效,而不会影响其他组件的样式,包括子组件。

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

html 复制代码
<!-- 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 样式类:

html 复制代码
<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 样式类,将文字颜色设为红色:

html 复制代码
<!-- 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 布局,内容居中:

xml 复制代码
<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 选择器中添加这个属性,从而实现样式的局部作用域

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

  1. 当 Vue 编译器编译一个包含 scoped<style> 标签时,会为每个 CSS 规则添加一个独特的属性,比如 data-v-4533200f。这个属性是自动生成的,确保了在整个应用中的唯一性。
  1. 编译器将模板中的每一个 HTML 标签都添加上相同的属性。这样,当浏览器解析和应用 CSS 样式时,只有带有这个属性的元素才会被这些规则影响,实现了样式的局部作用域。
  1. 对于子组件,它们的根元素也会被添加上父组件的属性,所以父组件的 scoped 样式可以影响到子组件的根节点。然而,这个属性不会被添加到子组件的内部元素,所以父组件的样式不会影响到子组件的内部样式。

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

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

4.1. 样式优先级问题

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

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

html 复制代码
    <!-- 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>
html 复制代码
    <!-- 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() 包括需要穿透的类,达到影响子元素的效果。

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

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

相关推荐
秋名山大前端6 小时前
Chrome GPU 加速优化配置(前端 3D 可视化 / 数字孪生专用)
前端·chrome·3d
今天不要写bug6 小时前
antv x6实现封装拖拽流程图配置(适用于工单流程、审批流程应用场景)
前端·typescript·vue·流程图
luquinn6 小时前
实现统一门户登录跳转免登录
开发语言·前端·javascript
用户21411832636026 小时前
dify案例分享-5分钟搭建智能思维导图系统!Dify + MCP工具实战教程
前端
augenstern4166 小时前
HTML(面试)
前端
excel6 小时前
前端常见布局误区:1fr 为什么撑爆了我的容器?
前端
烛阴6 小时前
TypeScript 类型魔法:像遍历对象一样改造你的类型
前端·javascript·typescript
vayy6 小时前
uniapp中 ios端 scroll-view 组件内部子元素z-index失效问题
前端·ios·微信小程序·uni-app
专注API从业者7 小时前
基于 Node.js 的淘宝 API 接口开发:快速构建异步数据采集服务
大数据·前端·数据库·数据挖掘·node.js
前端无冕之王7 小时前
一份兼容多端的HTML邮件模板实践与详解
前端·css·数据库·html