Vue3 style CSS 变量注入的实现

摘要

在单文件组件样式中支持使用组件状态驱动的 CSS 变量( CSS 自定义属性)。

基础示例







动机

Vue SFC 样式提供了直接的 CSS 搭配和封装,但它是纯粹的静态的 —— 这意味着到目前为止,我们没有能力在运行时根据组件的状态动态更新样式。

现在,随着大多数现代浏览器支持原生 CSS 变量,我们可以利用它来轻松连接组件的状态和样式。

设计细节

SFC 中的标签现在支持一个自定义 CSS 函数 v-bind:



正如预期的那样,这将把声明的值绑定到组件状态的属性上,reactively.color color

该函数内部可以支持任意的 JavaScript 表达式,但由于 JavaScript 表达式可能包含在 CSS 标识符中无效的字符,因此在大多数情况下需要用引号来包裹它们:v-bind

.text {
  font-size: v-bind ('theme.font.size');
}

当检测到这种 CSS 变量时,SFC 编译器将执行以下操作:

重写到一个带有哈希变量名称的本机。上面的内容将被改写为:v-bind () var ()

.text {
  color: var (--6b53742-color);
  font-size: var (--6b53742-theme_font_size);
}

请注意,hash 将应用于所有情况,无论标签是否有范围。这意味着注入的 CSS 变量不会意外地泄漏到子组件中。

相应的变量将作为内联样式被注入到组件的根元素中。对于上面的例子,最终渲染的 DOM 将看起来像这样:

hello

注入是响应式的 ——所以如果组件的属性发生变化,注入的 CSS 变量将被相应地更新。这种更新是独立于组件的模板更新的,所以对一个纯 CSS 的响应式属性的改变不会触发模板的重新渲染。

编译细节

为了注入 CSS 变量,编译器需要生成并注入如下代码到组件的 setup ()

import { useCssVars } from 'vue'

export default {
  setup() {
    //...
    useCssVars(_ctx => ({
      color: _ctx.color,
      theme_font_size: _ctx.theme.font.size,
    }))
  },
}

... 这里,运行时帮助器设置了一个将变量响应性地应用到 DOM.useCssVars watchEffect 上。

该编译策略要求脚本编译时首先对标签内容进行简单的重码解析,以确定要暴露的变量列表。然而,这个解析阶段不会像基于 AST 的完整解析