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

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

相关推荐
随云6327 分钟前
WebGL编程指南之着色器语言GLSL ES(入门GLSL ES这篇就够了)
前端·webgl
寻找09之夏1 小时前
【Vue3实战】:用导航守卫拦截未保存的编辑,提升用户体验
前端·vue.js
多多米10052 小时前
初学Vue(2)
前端·javascript·vue.js
柏箱2 小时前
PHP基本语法总结
开发语言·前端·html·php
新缸中之脑2 小时前
Llama 3.2 安卓手机安装教程
前端·人工智能·算法
hmz8562 小时前
最新网课搜题答案查询小程序源码/题库多接口微信小程序源码+自带流量主
前端·微信小程序·小程序
看到请催我学习2 小时前
内存缓存和硬盘缓存
开发语言·前端·javascript·vue.js·缓存·ecmascript
blaizeer3 小时前
深入理解 CSS 浮动(Float):详尽指南
前端·css
编程老船长3 小时前
网页设计基础 第一讲:软件分类介绍、工具选择与课程概览
前端
编程老船长4 小时前
网页设计基础 第二讲:安装与配置 VSCode 开发工具,创建第一个 HTML 页面
前端