在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>
最终这屏幕上展示的是两行红色的文字,这就是父组件与子组件都定义了title-wrap的样式,导致子组件的样式被父组件所覆盖。
遇到这种情况,可以在style标签中添加scoped属性
<style lang="scss" scoped> .content { .title-wrap { font-size: 20px; color: red; } } </style>
scoped作用的阻止上层的css样式传递到下层,限制当前css作用域,使其只对当前组件生效。
知道了它的作用,下面我们在开深入看下它的实现。
前面的是没有添加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>
由于父组件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; }
通过 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>
module的用法也很简单,只要在style中增加module
属性即可。不同之处是它在布局中的引用,都需要添加前缀$style
。因为通过module作用的style都被保存到$style
对象中。我可以通过console查看它的具体引用名。
mounted() { console.log(this.$style) console.log(this.$style['title-wrap']) }
通过观察,发现引用名有一定的规律。都是已index开头,后面再接着style中定义的命名,最后再接个后缀。这里的index是父组件的文件名index.vue。所以通过module作用的style将会重新命名为:文件名原style名不定后缀。
这么命名又有什么好处呢?我们再来看下展示的效果
当我们在浏览的控制台查看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>
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>
更多module相关操作可以点击查看
总结
scoped与module都非常简单、易用,那么又该如何选择呢?
通过上面的使用对比,发现scoped不需要额外的知识,只要在style中定义scoped属性即可,使用非常简便。但它的局限性是适用于中小项目中。因为scoped作用的style对于我们来说不直观,对于快速查找定位,module更加合适,同时module对于style向下传递的控制权也非常灵活;额外的还有变量导出等便捷功能。
所以如果你是小项目中且低成本的使用,scoped更加适合;而对大项目module更加合适,虽然有一点学习成本,但对于用更好的控制权、可观性与定位速度来说也就不值一提。
作者:午后一小憩
链接:https://www.jianshu.com/p/b12941cf96b6
来源:简书