前言
在很多业务场景中,我们的style
样式可能会根据业务逻辑的变化而变化,这个时候大家最容易想到的方案就是多写几个class
类,根据不同场景应用不同的类,比如这样:
<div
:class="{
[$style.sign_day]: true,
[$style.sign_today]: getSignStatus(item) == 1,
[$style.sign_notyet_day]: getSignStatus(item) == 6,
[$style.sign_day_dark]: theme == 'dark',
}"
>
</div>
<style lang="scss" module>
.sign_day {
background: red;
}
.sign_today {
background: yellow;
}
.sign_notyet_day {
background: blue;
}
.sign_day_dark {
background: orange;
}
</style>
这样虽然也是一种不错的方式,但是如果类型有非常多的话,那么你就得在vue
模版里面写大量的判断表达式,并且在style
中写大量的class
类。
要是在style
中也可以直接使用script
中的JS变量,那么这种场景处理起来是不是会更方便一点呢?
Vue2 CSS变量
在Vue2
中,遇到以上业务场景如果我们不想写大量的class
类的话,可以借助css
中的var()
函数来实现
var()
可以插入一个自定义属性(有时也被称为“CSS 变量”)的值,用来代替非自定义属性中值的任何部分。
比如:
在模版中调用getStyle
函数获取颜色值,并且定义成css变量
<div
v-for="item in signList"
:key="item.day"
:class="$style.sign_day"
:style="{ '--color': getStyle(item) }"
>
{{ item.title }}
</div>
生成颜色值
getStyle(item) {
switch (item.status) {
case 0:
return '#f8ae00'
case 1:
return '#e5353e'
case 2:
return '#1fddf4'
case 3:
return '#1ff46a'
default:
return '#191919'
}
},
然后就可以只写一个css类
了
.sign_day {
width: calc((100vw - 72px) / 4);
height: 80px;
margin-top: 8px;
border-radius: 8px;
display: flex;
justify-content: center;
align-items: center;
background-color: #f5f5f5;
color: var(--color);
}
这种方案的原理其实就是借助了CSS的自定义变量以及CSS的作用域来实现的
所以它需要两步:
- 自定义CSS变量(考虑作用域范围)
- 使用CSS变量
实际上在Vue3中还有更简便的方案!
Vue3 v-bind()
在Vue3单文件组件的<style>
标签支持使用v-bind
函数将 CSS 的值链接到组件中的数据。
所以以上场景还可以这样实现:
模版:
<div :class="$style.day_item">
{{ dayItem.title }}
</div>
计算颜色值:
const color = computed(() => {
switch (props.dayItem.status) {
case 0:
return '#f8ae00'
case 1:
return '#e5353e'
case 2:
return '#1fddf4'
case 3:
return '#1ff46a'
default:
return '#191919'
}
})
style 调用v-bind()
使用setup中的变量
<style lang="scss" module>
.day_item {
color: v-bind(color);
}
</style>
从该图我们可以发现Vue3中的v-bind()
原理与上面的CSS变量的原理一样,都是借助了CSS的自定义变量以及CSS的作用域来实现的
只不过不同的是v-bind()
生成的CSS变量前面多了一串hash
Vue3是如何编译v-bind()的?
猜测流程
我们可以从编译结果来进行反推
首先是我们的JS部分,编译成了以下内容:
这里会比没使用v-bind()
的组件多出一个_useCssVars()
函数
_useCssVars((_ctx) => ({
"5d92a9f9-color": color.value
}));
能不能猜到这个函数的作用是什么?如果不能,接着看下面一张图👇
这张图是组件的style
部分编译之后的产物,可以看到
.day_item {
color: v-bind(color);
}
编译成了
"._day_item_1oe25_1 {\n color: var(--5d92a9f9-color);\n}"
也就是说我们使用的v-bind
最终也是编译成了原生CSS中var
函数,原理也是使用CSS的自定义变量
但是这里只有使用,并没看到css变量定义
的地方🤔,现在能够猜测到_useCssVars()
函数的作用是什么吗?大概率就是用来生成css自定义变量
了。
接下来我们可以到源码中进行验证:
源码验证
- 找到源码中的
doCompileStyle
函数,打上断点,然后就可以启动debug
模式了
- 接着往下走你会看到一个
shortId
变量,它此时的值是什么呢?
是不是有点眼熟,没错它就是后面会出现在CSS变量前面的那一串hash
- 再接着往下走,我们可以看到
postcss
插件中添加了一个cssVarsPlugin
插件
这个插件的作用大家是不是已经猜到是干嘛的了,接着往下走
- 在
cssVarsPlugin
这个方法中再加一个断点
可以看到此时进来的decl
参数是:color: v-bind(color)
熟悉postcss
的同学应该能知道decl
是什么意思,它表示的是css转化为AST
后的一个节点类型
const vBindRE = /v-bind\s*\(/g;
将CSS声明中的属性值v-bind(color) 经过vBindRE
正则进行检测是否为v-bind()
语句
再往下,这里就是v-bind()
语句编译的核心代码了
首先是提取变量名
这里可以看到,执行后的结果是'color'
,也就是v-bind()
括号中的这个变量了
再往下
此时就能看到整个编译结果了:v-bind(color)
---> var(--5d92a9f9-color)
可以看到v-bind()
的编译其实就是通过正则处理重新生成字符串
现在知道v-bind()
是如何编译的,剩下一个重点就是:Vue是如何把style
中使用的变量转换成CSS变量
并设置在对应dom节点上的
这个突破点在我们上面猜测流程的第一张图,里面有这样一段代码:
_useCssVars((_ctx) => ({
"5d92a9f9-color": color.value
}));
很明显,它就是用来生成CSS变量
- 接下来我们可以在源码中找到这个函数,并打上断点
在源码中搜索_useCssVars
,你会发现什么也搜不到,这时我们可以尝试去掉_
仔进行搜索,你会发现有这样一段代码:
const CSS_VARS_HELPER = `useCssVars`;
很明显,后面在源码中我们只需要搜索CSS_VARS_HELPER
就可以,找到以下代码,打上断点,刷新页面
我们会发现这一段其实就是生成了我们上面那一段代码:
_useCssVars((_ctx) => ({
"5d92a9f9-color": color.value
}));
走到这里你会发现好像走不下去了,没有下一步了,因为最终我们看到的编译后的代码就是这个,具体是怎么把style
中使用的变量转换成CSS变量
并设置在对应dom节点上的这个并不是在编译时处理的。
想搞清楚这个我们还得在运行时打断点调试(这里换成了火狐浏览器进行断点调试,不要问为什么,问就是断点调试比谷歌好用)
接着往下走,会来到setVars
方法这里
从方法名我们一眼就能看出它就是用来设置CSS变量的!
再往下走setVars
-> setVarsOnVNode
-> setVarsOnNode
在这里最终会调用setProperty
方法来设置css变量。
到这里整个流程就结束了!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。