37

clipboard.png

一个web应用是离不开html、css与js,其中css充斥的整个web项目中。css它有一个特定,它是全局的。这样的特性导致的结果是,一旦你在不同的地方定义了相同的css命名,那么它们的样式就会相互覆盖,最终导致的style错乱,从而影响整个网页布局。

我相信对于每一个前端开发者都遇到过这种css样式覆盖的情况,值得庆幸的是,这些问题前辈都已经给出了解决方案。

在Vue中我们通过Scoped与Module来解决。下面我会分别对scoped与module解决方案进行说明,最后在分析它们的利弊与选择。如果你还未使用过或者说对它们之间的利弊与选择存在疑问的,相信这篇文章能够帮你解惑。

Scoped

假设我们有如下一段代码:

index.vue

<template>
  <div class="content">
    <div class="title-wrap">我是红色的</div>
    <green-title></green-title>
  </div>
</template>
 
<style lang="scss">
.content {
  .title-wrap {
    font-size: 20px;
    color: red;
  }
}
</style>

GreenTitle.vue

<template>
  <div class="content">
    <div class="title-wrap">我是绿色的</div>
  </div>
</template>
 
<style lang="scss">
.content {
  .title-wrap {
    font-size: 20px;
    color: green;
  }
}
</style>

clipboard.png

最终这屏幕上展示的是两行红色的文字,这就是父组件与子组件都定义了title-wrap的样式,导致子组件的样式被父组件所覆盖。

遇到这种情况,可以在style标签中添加scoped属性

<style lang="scss" scoped>
.content {
  .title-wrap {
    font-size: 20px;
    color: red;
  }
}
</style>

clipboard.png

scoped作用的阻止上层的css样式传递到下层,限制当前css作用域,使其只对当前组件生效。

知道了它的作用,下面我们在开深入看下它的实现。

clipboard.png

clipboard.png

前面的是没有添加scoped的源码,后面是添加了scoped的源码。我们进行一一对比,发现前面的两个div标签都使用了title-wrap样式,自然导致样式覆盖;而后面的两个div标签,第一个增加了data-v-67e6b31f的前缀,这就是父组的style中增加scoped的效果,区别与第二个div中的title-wrap样式。

scoped的实现是借助了PostCSS实现的,一旦增加了scoped,他会将之前覆盖的样式转换成下面的样式

<style lang="scss">
.content[data-v-67e6b31f] {
  .title-wrap[data-v-67e6b31f] {
    font-size: 20px;
    color: red;
  }
}
</style>

通过这种转换方式,间接的改变了原有的css命名。防止上层组件样式覆盖下层组件样式。

特性

细心的读者可能会发现上面的后一张源码图中第二个div的content中也有data-v-67e6b31f,可能会疑问,第二个content不是子组件中的css吗?子组件中未添加scoped,为什么还会添加data-v-67e6b31f前缀?

这是scoped的一个特性,使用 scoped 后,父组件的样式将不会渗透到子组件中。不过一个子组件的根节点会同时受其父组件有作用域的 CSS 和子组件有作用域的 CSS 的影响。这样设计是为了让父组件可以从布局的角度出发,调整其子组件根元素的样式。

所以如果我们将子组件做如下修改

<template>
  <!-- <div class="content"> -->
    <div class="title-wrap">我是绿色的</div>
  <!-- </div> -->
</template>

clipboard.png

clipboard.png

由于父组件scoped特性,所以会影响到子组件的title-wrap,也会添加data-v-67e6b31f前缀

那么又有个疑问,增加了scoped是否就一定不能传递的下层组件呢?毕竟我们可能有需要个别样式传递到下层的需求。别急,接着看,这个也能很方便的解决。

深度作用

如果你希望scoped中的某个样式能够作用的更深,影响到子组件,你可以使用>>>操作符

<style scoped>
.content >>> .title-wrap {
    font-size: 20px;
    color: red;
}
</style>

注意看我将style中的lang="scss"去掉了,因为加了预处理器后无法正确解析>>>,这种情况可以使用/deep/代替,本质是>>>的别名

<style lang="scss" scoped>
.content {
  /deep/ {
    .title-wrap {
      font-size: 20px;
      color: red;
    }
  }
}
</style>

将会编译成

.content[data-v-67e6b31f] .title-wrap {
    font-size: 20px;
    color: red;
}

clipboard.png

通过 v-html 创建的 DOM 内容不受作用域内的样式影响,但是你仍然可以通过深度作用选择器来为他们设置样式

Module

针对上面的覆盖问题,还可以通过设置module来解决

<template>
  <div :class="$style.content">
    <div :class="$style['title-wrap']">我是红色的</div>
    <green-title></green-title>
  </div>
</template>
 
<style lang="scss" module>
.content {
  .title-wrap {
    font-size: 20px;
    color: red;
  }
}
</style>

clipboard.png

module的用法也很简单,只要在style中增加module属性即可。不同之处是它在布局中的引用,都需要添加前缀$style。因为通过module作用的style都被保存到$style对象中。我可以通过console查看它的具体引用名。

mounted() {
  console.log(this.$style)
  console.log(this.$style['title-wrap'])
}

clipboard.png

通过观察,发现引用名有一定的规律。都是已index开头,后面再接着style中定义的命名,最后再接个后缀。这里的index是父组件的文件名index.vue。所以通过module作用的style将会重新命名为:文件名_原style名_不定后缀。

这么命名又有什么好处呢?我们再来看下展示的效果

clipboard.png

当我们在浏览的控制台查看Elements时,优点显而易见。相对于scoped的方式,module的方式能够一眼知道该元素时属于哪个文件组件中。在大型项目中能够帮助我们迅速定位到要查找的组件。

除了上述的快速定位,由于module会将所有的style都归入$style中,所以我们可以很灵活的将任意的父组件样式传递到任意深层的子组件中。例如,将父组件中的title-wrap通过props传递到子组件中

<template>
  <div :class="$style.content">
    <div :class="$style['title-wrap']">我是红色的</div>
    <green-title :styleTitle="$style['title-wrap']"></green-title>
  </div>
</template>
<template>
  <div class="content">
    <div :class="styleTitle">我是绿色的</div>
  </div>
</template>
<script>
 
export default {
  props: {
    styleTitle: String,
  },
}
</script>

clipboard.png

clipboard.png

module还有一个特性非常不错,它可以导出定义的变量,将变量归入$style中,例如:

<template>
  <div :class="$style.content">
    <div :class="$style['title-wrap']">我是红色的</div>
    <green-title :styleTitle="$style['title-wrap']"></green-title>
    <div>{{$style.titleColor}}</div>
  </div>
</template>
 
<style lang="scss" module>
$title-color: red;
:export {
  titleColor: $title-color
}
.content {
  .title-wrap {
    font-size: 20px;
    color: $title-color;
  }
}
</style>

clipboard.png

更多module相关操作可以点击查看

总结

scoped与module都非常简单、易用,那么又该如何选择呢?

通过上面的使用对比,发现scoped不需要额外的知识,只要在style中定义scoped属性即可,使用非常简便。但它的局限性是适用于中小项目中。因为scoped作用的style对于我们来说不直观,对于快速查找定位,module更加合适,同时module对于style向下传递的控制权也非常灵活;额外的还有变量导出等便捷功能。

所以如果你是小项目中且低成本的使用,scoped更加适合;而对大项目module更加合适,虽然有一点学习成本,但对于用更好的控制权、可观性与定位速度来说也就不值一提。

公众号

感觉不错的可以来一波关注,扫描下方二维码,关注公众号:怪谈时间,及时获取最新知识技巧与互联网新动态。

clipboard.png


午后一小憩
2.9k 声望838 粉丝