Vue:scoped与module的使用与利弊

一个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>

最终这屏幕上展示的是两行红色的文字,这就是父组件与子组件都定义了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更加合适,虽然有一点学习成本,但对于用更好的控制权、可观性与定位速度来说也就不值一提。

公众号

感觉不错的可以来一波关注,微信搜索关注公众号:怪谈时间,及时获取最新知识技巧与互联网新动态。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容