Vue 中慎用 style 的 scoped 属性

发布于 2022-10-18 21:02:02 字数 5556 浏览 149 评论 0

在 vue 组件中,在 style 标签上添加 scoped 属性,以表示它的样式作用于当下的模块,很好的实现了样式私有化的目的,这是一个非常好的机制。但是为什么要慎用呢?在实际业务中我们往往会对公共组件样式做细微的调整,如果添加了 scoped 属性,那么样式将会变得不易修改。初写这篇文章时,本人没有找到一个好的方法去解决这个问题,后来经过大伙的解答,才让我恍然大悟。

何为谨慎使用

谨慎使用不是不用,而是持一种审视的目光去看待它。scoped 肯定是解决了样式私有化的问题,但同时也引入了新的问题—样式不易(可)修改,而很多时候,我们是需要对公共组件的样式做微调的。所以我才说要谨慎使用

解决方案

首先要说明的问题是,最开始我以为这是一个 BUG 或者说一个弊端 (因为当时没有搞明白 scoped 的真正作用),就很英勇的去提了一个 issue,然后理所当然的被关闭了,关闭的理由是:scoped 设计的初衷就是让样式变得私有,让它不会影响其他任何地方的样式。但是由于我在业务中经常遇到需要修改有 scoped 属性的组件,就写了一篇文章记录一下这个问题,希望大家谨慎的使用这个属性。

然而事实再一次证明了我的愚蠢,在 vue-loader 的文档中已经详细的对这个问题做了分析,并且对我遇到这种问题给出了解决方法:vue-loader 的深度作用选择器。
因为我并没有去深入了解这些问题,所以注定这篇文章被大伙拍砖。

解决方案:vue-loader 之 scoped-css

scoped 实现私有化样式的原理

为什么会说,会增加复杂度?那么我们先从的实现模块的原理说起。为了方便称呼,我们假设把这种组件叫做模块私有组件,其他的未加 scoped 的叫做模块一般组件。
通过查看 DOM 结构发现:vue 通过在 DOM 结构以及 css 样式上加唯一不重复的标记,以保证唯一,达到样式私有化模块化的目的。具体的渲染结果是怎样的,通过一个例子来说明。

公共组件 button 组件

一个公共组件 button,为了样式模块化,给其加上 scoped 属性,

<template>
  <div class="button-warp">
    <button class="button">text</button>
  </div>
</template>
...
<style scoped>
  .button-warp{
    display:inline-block;
  }
  .button{
    padding: 5px 10px;
    font-size: 12px;
    border-radus: 2px;
  }
</style>

浏览器渲染 button 组件

button 组件在浏览器渲染出的 html 部分和 css 部分分别为:

<div data-v-2311c06a class="button-warp">
  <button data-v-2311c06a class="button">text</button>
</div>
.button-warp[data-v-2311c06a]{
  display:inline-block;
}
.button[data-v-2311c06a]{
  padding: 5px 10px;
  font-size: 12px;
  border-radus: 2px;
}

从上面的字可以看出,添加了 scoped 属性的组件,为了达到组件样式模块化,做了两个处理:

给 HTML 的 DOM 节点加一个不重复 data 属性 (形如:data-v-2311c06a) 来表示他的唯一性在每句 css 选择器的末尾(编译后的生成的 css 语句)加一个当前组件的 data 属性选择器(如[data-v-2311c06a])来私有化样式大家都知道 css 样式有一个优先级的说法,scoped 的这一操作,虽然达到了组件样式模块化的目的,但是会造成一种后果:每个样式的权重加重了:理论上我们要去修改这个样式,需要更高的权重去覆盖这个样式。这是增加复杂度的其中一个维度。

其他组件引用 button 组件

上面分析了单个组件渲染后的结果, 那么组件互相调用之后会出现什么样的结果呢?,具体分两种情况:模块一般组件引用模块私有组件(本质和模块私有组件引用模块一般组件一样);模块私有组件引用模块私有组件。

举个例子:在组件 content.vue 中使用了 button 组件,那么 content.vue 组件是否添加 scoped 属性渲染出来的结果有什么区别呢?

<template>
  <div class="content">
    <p class="title"></p>
    <!-- v-button假设是上面定义的组件 -->
    <v-button></v-button>
  </div>
</template>
...
<style>
  .content{
    width: 1200px;
    margin: 0 auto;
  }
  .content .button{
    border-raduis: 5px;
  }
</style>

模块一般组件(未添加 scoped)引用模块私有组件
如果 style 上没有加 scoped 属性,那么渲染出来 html 和 css 分别就是:

<div class="content">
  <p class="title"></p>
  <!-- v-button假设是上面定义的组件 -->
  <div data-v-2311c06a class="button-warp">
    <button data-v-2311c06a class="button">text</button>
  </div>
</div>

.button-warp[data-v-2311c06a]{
  display:inline-block;
}
.button[data-v-2311c06a]{
  padding: 5px 10px;
  font-size: 12px;
  border-radus: 2px;
}

.content{
  width: 1200px;
  margin: 0 auto;
}
.content .button{
  border-raduis: 5px;
}

可以看出,虽然在 content 组件中,修改了 button 的 border-raduis 属性,但是由于权重关系,生效的依然是组件内部的样式(此时是外部的样式被覆盖)。所以如果要达到修改样式的目的,就必须加重我们要修改样式的权重(增加选择器层级,ID 选择器,并列选择器,impotant 等)

模块私有组件(添加 scoped)引用模块私有组件

如果加了 scoped 属性呢?按照开始分析出来的规则(事实也是这么的):
首先是在所有的 DOM 节点加上 data 属性
然后在 css 选择器尾部加上 data 属性选择器

那么渲染出来 html 和 css 分别就是:

<div data-v-57bc25a0 class="content">
  <p data-v-57bc25a0 class="title"></p>
  <!-- v-button假设是上面定义的组件 -->
  <div data-v-57bc25a0 data-v-2311c06a class="button-warp">
    <button data-v-2311c06a class="button">text</button>
  </div>
</div>

.button-warp[data-v-2311c06a]{
  display:inline-block;
}
.button[data-v-2311c06a]{
  padding: 5px 10px;
  font-size: 12px;
  border-radus: 2px;
}

.content[data-v-57bc25a0]{
  width: 1200px;
  margin: 0 auto;
}
.content .button[data-v-57bc25a0]{
  border-raduis: 5px;
}

对于上面的两种情况,可以明显看出来渲染后的结果大不相同。
虽然我们在 content 添加了想要修改 button 组件的样式的代码,但是仔细看,由于. content .button 这句在末尾加的是 content 组件的 scoped 标记,最后这句其实根本作用不到我们想要的 DOM 节点上,所以这种情况我们在 content 内部写的任何样式都不会影响到 button.vue 组件,所以这就尴尬了。。。。
当然这个问题也是可以解决的,就是直接加全局样式可以修改到,但这势必会影响全部地方的组件;所以需要另外一种方法在 content.vue 组件内再加一个不带 scoped 属性的 style 标签,也就意味着要加两个 style,一个用于私有样式,一个用于共有样式。这肯定是有点 shit 的,并且这两种解决方案都回避不了一个问题:权重!!!

<template>
  <div class="content">
    <p class="title"></p>
    <!-- v-button假设是上面定义的组件 -->
    <v-button></v-button>
  </div>
</template>
...
<style scoped>
  .content{
    width: 1200px;
    margin: 0 auto;
  }
</style>
<style>
  .content .button{
    border-raduis: 5px;
  }
</style>

这样符合规范么?貌似没看到不能这么写,并且这么写也确实生效了。。。但这样确实增加了思维的复杂度,有点苦恼啊。

总结 scoped 的渲染规则

总结一下 scoped 三条渲染规则

给 HTML 的 DOM 节点加一个不重复 data 属性 (形如:data-v-2311c06a) 来表示他的唯一性
在每句 css 选择器的末尾(编译后的生成的 css 语句)加一个当前组件的 data 属性选择器(如[data-v-2311c06a])来私有化样式
如果组件内部包含有其他组件,只会给其他组件的最外层标签加上当前组件的 data 属性

解决方案

对于引用的三方库,如果对方使用了 scoped,我们无力改变什么,如果确实需要修改他的样式最能在不加 scoped 的组件中修改样式,或者全局样式直接修改,这很粗暴!对于自己维护的组件,一定要想清楚,组件的样式能否满足所有的情况。如果确实需要加,无疑会增加使用这个组件的开发同学的工作!

当然对于这个问题,如果诸君有更好的解决方案,请诸君 TELL ME 一下下!

趣事

在使用 scoped 一定要谨慎 scoped 的这个特性,本人以为这是一个 BUG,就去提了 issue ,结果尤大很霸气的回复,scoped 设计的初衷就是不能让当前组件的样式修改其他任何地方的样式,因为设计如此。所以理所当然的这个 issue 已被干掉。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

很酷不放纵

暂无简介

文章
评论
27 人气
更多

推荐作者

櫻之舞

文章 0 评论 0

弥枳

文章 0 评论 0

m2429

文章 0 评论 0

野却迷人

文章 0 评论 0

我怀念的。

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文