Vue scoped样式与CSS Module 对比

在现代化的 Web 开发中,CSS 还远未完美,这一点应该没有什么意外。

现今的项目通常都相当复杂,而 css 样式天生又是全局性的,所以到最后总是极容易地就发生样式冲突——要么是样式相互覆盖,要么就是隐式地级联到了下面那些我们未考虑到的元素。

在减轻 CSS 存在的主要痛点方面,我们普遍采用的解决方案是引入 BEM (Block Element Modifier) 方法学。不过这只能解决我们这个大问题的很小一部分。

我们非常幸运,社区已经开发出了一些解决方案,他们可以帮我们处理这些问题。说不定你已经听说过了 CSS Modules、Styled Components、Glamorous、JSS——这些只是众多流行的工具中的少数几个。如果你对这个话题感兴趣,你可以查看这篇帖文——作者 Indrek Lasn 对 CSS-in-JS 的思想做了非常详尽的讲解。

每个通过 vue-cli 创建的 Vue.js 应用都内置了两个很好的解决方案:Scoped CSS 和 CSS Modules (模块式 CSS)。两种方案各有优缺点,所以下面我们就仔细看下哪种方案在你的案例中更适用。

Scoped 样式

我们只需要在 style 标签上添加一个 scoped 属性即可启用 scoped 样式:

<template>
  <button class=”button” />
</template>
<style scoped>
  .button {
    color: red;
  }
</style>

这样就会使得我们的样式只被应用到这个组件中的元素上。这是借助 PostCSS 实现的,它会将上面的代码转换成下面这样:

<style>
.button[data-v-f61kqi1] {
  color: red;
}
</style>
<button class=”button” data-v-f61kqi1></button>

就像你看到的这样,整个过程不需要做什么就可以达到很好的 scoped 样式效果。

现在假设你需要调整一个视图中的某个组件的宽度,那么你可以像你平时那样做的一样:在这个组件上添加一个额外的 class 来设置其样式。

<template>
  <BasePanel class=”pricing-panel”>
    content  </BasePanel>
</template>
<style scoped>
  .pricing-panel {
    width: 300px;
    margin-bottom: 30px;
  }
</style>

经转换后:

<style>
  .base-panel[data-v-d17eko1] {
    ...
  }
  .pricing-panel[data-v-b52c41] {
    width: 300px;
    margin-bottom: 30px;
  }
</style>
<div class=”base-panel pricing-panel” data-v-d17eko1 data-v-b52c41>
  content</div>

这次还是一样,不需要做什么你就获得了对布局的彻底控制。

不过请注意:这个特性存在一个缺陷,即如果你子组件的根元素上有一个类已经在这个父组件中定义过了,那么这个父组件的样式就会泄露到子组件中。如果想更好地理解这个问题,可以查看这个 CodeSandbox 例子。

还有一些情况是我们需要对我们的子组件的深层结构设置样式——虽然这种做法并不受推荐且应该避免。为了简便起见,我们假设我们的父组件现在要对 BasePanel 的标题设置样式,在 scoped 样式中,这种情况可以使用 >>> 连接符(或者 /deep/ )实现。

<style scoped>
  .pricing-panel >>> .title {
    font-size: 24px;
  }
</style>

经转换后:

.pricing-panel[data-v-b52c41] .title {
  font-size: 24px;
}

非常简单,是吧?可是别忘记,我们却因此失去了组件的封装效果。这个组件内的所有的 .title 类的样式都会被这些样式所浸染——即便是孙节点。

模块式 CSS

模块式 CSS 的流行源于 React 社区,它获得了社区的迅速的采用。

Vue.js 更甚之,其强大、简便的特性在加上通过 vue-cli 对其开箱即用的支持,将其发展到另一个高度。

现在让我们来看下怎么使用它:

<style module>
  .button {
    color: red
  }
</style>

这次我们使用的不是 scoped 属性,而是 module。这等于告诉 vue-template-compiler 和 vue-cli 的 webpack 配置要对这一部分采用哪些相应的 loader,进而生成像下面这样的 CSS:

.ComponentName__button__2Kxy {
  color: red;
}

它的特殊之处以及和 scoped 样式不一样的地方就在于所有创建的类可以通过这个组件的 $style 对象获取。因此,要将这个类进行应用,我们需要像下面这样进行 class 绑定:

<template>
  <button :class="$style.button" />
</template>
<style module>
  .button {
    color: red
  }
</style>

这段代码将生成下面的 HTML 及相关的样式:

<style>
  .ComponentName__button__2Kxy {
    color: red;
  }
</style>
<button class=”ComponentName__button__2Kxy”></button>

它的第一点好处就是,当我们在 HMTL 中查看这个元素时我们可以立刻知道它所属的是哪个组件;第二点好处是,一切都变成显式的了,我们拥有了彻底的控制权——不会再有什么奇怪的现象了。和 scoped 样式那种把普通的标签也加上那些 data 属性的做法不一样,这些普通标签在转换后还是最初的样子。

比较 scoped 样式中的第二个例子,我们来看下我们可以怎么对那个组件设置样式:

<template>
  <BasePanel :class="$style['pricing-panel']">
    content  </BasePanel>
</template>
<style module>
  .pricing-panel {
    width: 300px;
    margin-bottom: 30px;
  }
</style>

其转换后:

<style>
  .BasePanel__d17eko1 {
    /* some styles */
  }
  .ComponentName__pricing-panel__a81Kj {
    width: 300px;
    margin-bottom: 30px;
  }
</style>
<div class="BasePanel__d17eko1 ComponentName__pricing-panel__a81Kj">
  content</div>

毫无意外,跟我们期望的结果一样。此外,因为所有的 CSS 类可以通过 $style 对象获取到,所以我们可以通过 props 将这些类传递到任何我们希望的深度中,这样,在子组件中的任意位置使用这些类就会变得极其容易:

<template>
  <BasePanel
    title="Lorem ipsum"
    :titleClass="$style.title"
  >
    Content  </BasePanel>
</template>

模块式 CSS 与 JS 有着很好的互操作性 (interoperability),这一点不只局限于 CSS 类。我们还可以使用 :export 关键字将其他的东西导出到 $style 对象上。

例如,想象一下你有一个图表需要开发 —— 你可以在 CSS 中定义你的色彩变量的同时将其导出,以供你的组件使用:

<template>
  <div>{{ $style.primaryColor }}</div> <!-- #B4DC47 -->
</template>
<style module lang="scss">
  $primary-color: #B4DC47;

  :export {
    primaryColor: $primary-color
  }
</style>

对于模块式 CSS的概念,我这里还只是讲到了它的皮毛,它实际要宽泛的多,建议你查看下它完整的规范以了解更多。

总结

其实两种方案都非常简单、易用,在某种程度上解决的是同样的问题。 那么你该选择哪种呢?

scoped 样式的使用不需要额外的知识,给人舒适的感觉。它所存在的局限,也正是它的使用简单的原因。它可以用于支持小型到中型的应用。

在更大的应用或更复杂的场景中,这个时候,对于 CSS 的运用,我们就会希望它更加显式,拥有更多的控制权。虽然在模板中大量使用 $style 看起来并不那么“性感”,但却更加安全和灵活,为此我们只需付出微小的代价。还有一个好处就是我们可以用 JS 获取到我们定义的一些变量(如色彩值、样式断点),这样我们就无需手动保持其在多个文件中同步。
求点赞,求关注~


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

推荐阅读更多精彩内容