Style中的v-bind——状态驱动的动态 CSS

3,828 阅读2分钟

首先向想学习Vue3源码的各位推荐一个学习利器

mini-vue

可观看视频版:使用详细指南

这个库把 Vue3 源码中最核心的逻辑剥离出来,只留下核心逻辑,以供大家学习。带有详细的中文注释,以及完善的输出,帮助用户理解运行时流程。


简单介绍

今天要讨论的是Vue3中的v-bind,当然不是写在Template中的v-bind,而是写在Stylev-bind

Vue3文档的末尾详细地介绍了这个API,它的用法也比较简单。

<script setup>
const theme = {
  color: 'red'
}
</script>

<template>
  <p>hello</p>
</template>

<style scoped>
p {
  color: v-bind('theme.color');
}
</style>

那么,它和原始的CSS变量 var()有什么不同呢?

与Var()的不同

当我们使用var()时都需要预先在使用节点的祖先节点中编写所需的CSS变量,如果想要该CSS变量与业务代码中的某些变量产生联动,则需要已内联的方式将JS变量写入节点的Style

<template>
    <p :style="cssVars"></p>
</template>

<script setup>
const textColor = '#fff'
const cssVars = {
   '--color': textColor
}
</script>

<style>
p {
  color: var(--color);
}
</style>

我们从官方给出的示例中可以看到,使用v-bind可以直接在Style中使用script抛出的变量,在开发过程中可谓是非常方便了。

那么它是怎么做到这一点的呢?

运行流程

graph TD
SFC单文件 --> vue-loader
vue-loader -->|正则匹配出`Style`中`v-bind`所使用的变量名称| cssVars --> compilerScript;
compilerScript -->|编译script|拿到cssVars;
拿到cssVars -->|遍历保存的变量名|useCssVar --> 写入组件的节点中;

源码分析

我们从一个简单的测试实例出发

image-20211221140257295

compileSFCScript是测试时使用的工具函数,主要操作只有两步:分析SFC文件、编译文件。

image-20211221141422442

parse中,会事先初始化一个描述对象

(compiler-sfc/src/parse.ts:112)

image-20211221154848343

之后将源码传入CompilerDOM.parse解析成vue专属的AST,通过对AST的children进行遍历,获取的templatescriptstyle等这些代码块,其中style可能会有多个的情况,所以desciptor.styles是个数组。

(compiler-sfc/src/parse.ts:155)

code2

获取到styles之后就要取得样式表中的css Vars

(compiler-sfc/src/parse.ts:263)

image-20211221155927072

(compiler-sfc/src/cssVars.ts:40)

image-20211221160851850

使用正则的方式取得v-bind中的值,储存在vars中并返回

至此就完成了style中值的获取

之后,在compileScript中,判断AST中的cssVars是否有值,有值则拼接上预先编译的文本

(compiler-sfc/src/compileScript.ts:201)

image-20211221161453588

(compiler-sfc/src/cssVars.ts:108)image-20211221161604497

最后的结果就是测试用例中的内容

image-20211221163644072

解析阶段的任务完成,之后就是使用阶段

在该组件被使用时,通过useCssVars这个API来挂载css变量

(runtime-dom/src/helpers/useCssVars.ts)

code3

使用时遇到的问题

  1. 与teleport配合 在使用<teleport>的组件中使用v-bind会使得移动了的节点找不到绑定在根节点中的css变量

  2. hash问题 绑定在节点上的变量默认会带有hash值,想要全局管理的css变量的话就不推荐这种方式了

image.png