前言
vue2的时候想必大家有遇到需要在style模块中访问script模块中的响应式变量,为此我们不得不使用css变量去实现。现在vue3已经内置了这个功能啦,可以在style中使用v-bind
指令绑定script模块中的响应式变量,这篇文章我们来讲讲vue是如何实现在style中使用script模块中的响应式变量。注:本文中使用的vue版本为3.4.19
。
欧阳平时写文章参考的多本vue源码电子书、加入欧阳的第一个vue源码交流群
看个demo
我们来看个简单的demo,index.vue文件代码如下:
<template>
<div>
<p>222</p>
<span class="block">hello world</span>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
const primaryColor = ref("red");
</script>
<style scoped>
.block {
color: v-bind(primaryColor);
}
</style>
我们在script模块中定义了一个响应式变量primaryColor
,并且在style中使用v-bind
指令将primaryColor
变量绑定到color样式上面。
我们在浏览器的network面板中来看看编译后的js文件,如下图:
从上图中可以看到在network面板中编译后的index.vue文件有两个,并且第二个里面有一些query参数,其中的type=style
就表示当前文件的内容对应的是style模块。第一个index.vue对应的是template和script模块中的内容。
我们来看看第一个index.vue,如下图:
从上图中可以看到setup函数是script模块编译后的内容,在setup
函数中多了一个_useCssVars
函数,从名字你应该猜到了,这个函数的作用是和css变量有关系。别着急,我们接下来会详细去讲_useCssVars
函数。
我们再来看看第二个index.vue,如下图:
从上图中可以看到这个index.vue确实对应的是style模块中的内容,并且原本的color: v-bind(primaryColor);
已经变成了color: var(--c845efc6-primaryColor);
。
很明显浏览器是不认识v-bind(primaryColor);
指令的,所以经过编译后就变成了浏览器认识的css变量var(--c845efc6-primaryColor);
。
我们接着在elements面板中来看看此时class值为block的span元素,如下图:
从上图中可以看到color的值为css变量var(--c845efc6-primaryColor)
,这个我们前面讲过。不同的是这里从父级元素div中继承过来一个--c845efc6-primaryColor: red;
。
这个就是声明一个名为--c845efc6-primaryColor
的css变量,变量的值为red
。
还记得我们在script模块中定义的响应式变量primaryColor
吗?他的值就是red
。
所以这个span元素最终color渲染出来的值就是red
。
接下来我们将通过debug的方式带你搞清楚在style中是如何将指令v-bind(primaryColor)
编译成css变量var(--c845efc6-primaryColor)
,以及_useCssVars
函数是如何生成声明值为red
的css变量--c845efc6-primaryColor
。
doCompileStyle
函数
在前面的文章中我们讲过了style模块实际是由doCompileStyle
函数函数处理的,具体如何调用到doCompileStyle
函数可以查看我之前的文章: 掉了两根头发后,我悟了!vue3的scoped原来是这样避免样式污染。
我们需要给doCompileStyle
函数打个断点,doCompileStyle
函数的代码位置在:node_modules/@vue/compiler-sfc/dist/compiler-sfc.cjs.js
。
还是一样的套路启动一个debug终端。这里以vscode
举例,打开终端然后点击终端中的+
号旁边的下拉箭头,在下拉中点击Javascript Debug Terminal
就可以启动一个debug
终端。
在debug终端执行yarn dev
,在浏览器中打开对应的页面,比如:http://localhost:5173/ 。
此时断点将停留在doCompileStyle
函数中,在我们这个场景中doCompileStyle
函数简化后的代码如下:
import postcss from "postcss";
function doCompileStyle(options) {
const {
filename,
id,
postcssOptions,
postcssPlugins,
} = options;
const source = options.source;
const shortId = id.replace(/^data-v-/, "");
const plugins = (postcssPlugins || []).slice();
plugins.unshift(cssVarsPlugin({ id: shortId, isProd }));
const postCSSOptions = {
...