Vue:Scoped Styles 和 CSS Modules 的区别
原文链接:Vue.js - Scoped Styles vs CSS Modules,2018.08.28,by Michał Sajnóg
导读:Vue.js 项目内置了两种样式方案:Scoped Styles 和 CSS Modules。Scoped Styles 是 Vue 独有的基于额外添加的 HTML attribute 实现的、类 shadow DOM 方案,更容易使用;CSS Modules 则最早由 React 社区采用,使用 JavaScript 语言管理页面中的样式,初写起来会有些麻烦,但更精确、安全和灵活。当然,具体采用哪种方案,需要读者根据项目实际情况做选择。
正文开始:
现代 Web 开发中的 CSS 远非完美,这并不令人意外。
如今的项目通常非常复杂,特别是在使用全局样式时,很容易出现样式冲突的问题,这些样式会互相覆盖或者隐含地级联到我们之前没有考虑过的元素上。
我们最常使用的一个解决方案是 BEM(Block Element Modifier)方法论。然而,它只解决了更大问题中的一小部分。
幸运的是,社区已经开发出很多可以帮助我们更彻底地处理问题的解决方案。你可能已经听说过 CSS Modules、Styled Components、Glamorous 或 JSS——这些只是我们今天可以添加到项目中最流行工具中的几个。如果你有兴趣,可以阅读 Indrek Lasn 在这篇文章中非常详细地解释了 CSS-in-JS 思想。
每个通过 vue-cli 创建的新 Vue.js 应用程序都配备有两种强大的内置解决方案:Scoped Styles 和 CSS Modules。它们各有优缺点,因此我们仔细看下并确定哪种解决方案更适合你。
Scoped Styles
为了让 Scoped Styles 起作用,我们只需要在 <style>
标签中添加一个 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>
正如你看到的,Vue 中的 Scoped Styles 不需要任何配置就能使用,当然它会以同样的方式处理作用域标签样式( scoping tags’styles)。
如果你需要更改特定视图中的组件宽度,则可以给组件添加额外类名,这会像给组件中的其他标签设置样式一样生成 Scoped Styles 的:
<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 Styles 中 >>>
这个选择器(或是 /deep/
)就派上用场了。
<style scoped>
.pricing-panel >>> .title {
font-size: 24px;
}
</style>
上述代码会被转换为:
.pricing-panel[data-v-b52c41] .title {
font-size: 24px;
}
简单明了,是吧?但请注意,这样我们就失去了封装性。任何在此组件内部使用的 .title
类的地方(甚至是子组件内的后代组件)都会受到影响。
CSS Modules
CSS 模块因 React 社区的快速使用而变得流行。Vue.js 通过使用 vue-cli 将其强大性能与易用性和开箱即用的支持相结合,使其达到了另一个水平。
现在让我们看看如何使用它:
<style module>
.button {
color: red
}
</style>
我们使用模块 module 来代替 scoped 属性,这会告诉 vue-template-compiler 和 vue-cli 的 webpack 配置使用适当的加载器来处理此部分样式并生成以下 CSS:
.ComponentName__button__2Kxy {
color: red;
}
它与 Scoped Styles 不同之处在于,所有创建的类都可以通过组件内的 $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>
第一个好处是,从类名上就能看出 HTML 中的这个元素,是属于哪个组件的。其次,一切都变得非常明确,我们拥有完全的控制权——没有任何魔法。然而,在样式化 HTML 标签时,我们必须小心谨慎,对比 Scoped Styles 将普通标签也限定在唯一数据属性范围内,CSS Modules 则会将标签原样输出。
与 Scoped Styles 中的第二个例子类似,请看如何在特定上下文中为组件设置样式:
<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>
它只是完成工作,没有任何意外!此外,由于所有类都可以通过$style
对象访问,所以可以使用 props 将 $style
传递到任何深度的位置中,实现在子组件的任何地方轻松使用一个类:
<template>
<BasePanel
title="Lorem ipsum"
:titleClass="$style.title"
>
Content
</BasePanel>
</template>
CSS Modules 与 JS 具有很好的互操作性,而且不限于类。使用 :export
关键字,我们还可以将其他内容导出到 $style
对象中。
想象一下你要开发一个图表,那么可以在 CSS 中保留颜色变量,并额外将它们导出供组件使用:
<template>
<div>{{ $style.primaryColor }}</div> <!-- #B4DC47 -->
</template>
<style module lang="scss">
$primary-color: #B4DC47;
:export {
primaryColor: $primary-color
}
</style>
总结
这两种解决方案都非常简单易用,而且在某种程度上解决了同样的问题。那么应该选择哪一个呢?
Scoped styles 不需要额外的知识就可以使用,并且感觉很舒适。它们的限制也使得它们易于使用,并且能够支持小到中型应用程序。
然而,在更复杂的场景和更大型的应用程序中,CSS Modules 的方式会更加明确并对 CSS 中发生的事情有更多控制。即使在模板中多次使用 $style
对象看起来可能不太美观,但为此付出一点代价以获得安全性和灵活性是值得的。我们还可以轻松访问 JS 中的变量(如颜色或断点),而无需将不同文件进行同步。
转载自:https://juejin.cn/post/7237324970556932153