Vue 中作用域 CSS 的那点事
前言
首先,我们来回忆一下「CSS 作用域」这一概念,它的本质是通过让每一个选择器成为一个「unique」的存在,这样就自然而然地形成了作用域。
而提到「Vue」中「作用域 CSS」,我想大家应该立即想到以 scoped
的方式形成的带有作用域的 css
。但是,值得一提的是,在「Vue」中还支持了一种「作用域 CSS」,即「CSS Module」。
提及 「CSS Module」,想必大家会有点陌生,相信有很多同学在平常开发中都是用 scoped
来实现「Vue」组件中的「作用域CSS」。所以,今天我们就来详细认知一下这两者。
一、scoped 作用域
「scoped 作用域」是「Vue」通过「postcss」来实现对每一个在 scoped
标签中定义的选择器的特殊作用域标识,例如:
<template>
<div id="app">
<div class="out-box">
</div>
</div>
</template>
<style lang="scss" scoped>
#app {
.out-box {
width: 200px;
height: 200px;
background-color: #faa;
}
}
</style>
标识后展示在页面上的:
<div data-v-7ba5bd90 id="app">
<div data-v-7ba5bd90 class="out-box">
</div>
</div>
<style>
#app .out-box[data-v-7ba5bd90] {
width: 200px;
height: 200px;
background-color: #faa;
}
</style>
可以看到,本质上是在原有的「选择器」的基础上通过「postcss」加上了一串 attr
。
并且,在我们平常开发中,很常见的场景就是我们在使用一些已有的组件或第三方组件时,我们需要对原有组件的样式进行一些微小的改动。那么,这个时候就需要使用穿透来实现样式的改动,例如:
<style>
div >>> .out-box {
background-color: #aaf;
}
</style>
这里需要注意的是 >>> 只是一种穿透方式,并不是所有场景下都是可以用 >>> 实现。例如,「iView」需要使用 /deep/ 的方式,「ElementUI」需要使用 ::v-deep 的方式。
二、CSS Module 作用域
相比较「scoped 作用域」,「CSS Modeul 作用域」它所具备的能力更强,所以内容也相对较多。
什么是 CSS Module
「CSS Module」指的是可以将一个定义好的「CSS」文件以变量的形式导入,然后通过使用这个变量对「HTML」中的元素进行样式的修饰,例如:
a.css
.box {
width: 100%;
height: 100%;
background-color: #afa;
}
b.js
import style from './a.css'
const boxElem = document.createElement('div');
boxElem.className = style.box
然后,渲染到页面的时候,它对应的 HTML 看起来会是这样:
<div class="a-box_jlpNx"></div>
可以看出,此时的「类选择器」同样是随机生成的,这也是「CSS Module」形成作用域的所在。
值得一提的是,「CSS Module」还具备其他的能力,例如可以定义全局的「选择器」,写起来会是这样:
:global(.title) {
color: green;
}
接下来的使用和局部的一样。至于,其他用法,有兴趣的同学可以去 GitHub 上自行阅读
CSS Module 在 Vue 中的应用
回到本文所说的,在「Vue」中也对「CSS Module」做了相应的支持,当我们在 style 标签上添加 module 属性时,「Vue」 会在当前「组件实例」上注入一个计算属性 $style
,然后我们可以通过 $style
来使用我们定义好的「选择器」,例如:
<template>
<div>
<div :class="$style['inner-box']"></div>
</div>
</template>
<style lang="scss" module>
.inner-box {
width: 100px;
height: 100px;
background-color: #aaf;
}
</style>
然后,它渲染到页面时,对于的 HTML 会是这样:
<div class="HelloWorld_inner-box_jlpNx"></div>
那么,这个时候,又回到和「scoped 作用域」一样的问题,使用了「CSS Module」来定义组件的样式,那么我在使用它的时候,如何进行覆盖?
标准的答案,对于「CSS Module」并没有覆盖的说法,有的只是为一个组件设置不同的主题。
但是,如果真的需要弄,那只能通过对该模块对应的
style
标签中定义你需要的样式,然后根据传入组件的props
来动态绑定class
那么,为组件设置主题,我们需要在设计组件的时候,对这个组件预留 props
,并将该 props
添加到 $style
中,然后在这个组件中的相应元素中使用,例如:
Box.vue 组件
<template>
<div>
<div :class="$style[themeColor]"></div>
</div>
</template>
<script>
export default {
props: {
themeColor: {
type: String,
default: 'line'
}
}
};
</script>
<style lang="scss" module>
.line {
width: 100px;
height: 100px;
background-color: #aaf;
}
.card {
background-color: #aaf;
}
</style>
然后,我们在使用该组件的时候通过 props
传入 line
或者 card
,从而实现切换组件不同的背景色。
三、两种方式的优劣势
「scoped 作用域」:
-
对组件没有硬性要求 -
不易于管理组件样式,需要借助第三方变量定义支持 -
易于覆盖组件样式,即通过穿透来实现对样式的覆盖
「CSS Module 作用域」:
-
适合于高度沉淀下的组件使用 -
易于管理组件样式,即可以通过 style
管理组件中的选择器 -
组件样式无法通过外部直接覆盖
这里所说的管理,是指通过
JavaScript
便捷地控制组件样式。
写在最后
其实,对比「scoped」和「CSS Module」两者,各有千秋。至于,要用哪一着得看具体需求,建议大项目中可以使用「CSS Module」,小项目的话用用「scoped」应该绰绰有余。
写作不易,如果你觉得有收获的话,可以帅气三连击!!!