深入理解Vue3:style中的响应式变量如何工作?

前言

在很多业务场景中,我们的style样式可能会根据业务逻辑的变化而变化,这个时候大家最容易想到的方案就是多写几个class类,根据不同场景应用不同的类,比如这样:

xml 复制代码
<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变量

ini 复制代码
<div
v-for="item in signList"
:key="item.day"
:class="$style.sign_day"
:style="{ '--color': getStyle(item) }"
>
  {{ item.title }}
</div>

生成颜色值

javascript 复制代码
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类了

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单文件组件的 标签支持使用 v-bind 函数将 CSS 的值链接到组件中的数据。

所以以上场景还可以这样实现:

模版:

css 复制代码
<div :class="$style.day_item">
  {{ dayItem.title }}
</div>

计算颜色值:

dart 复制代码
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中的变量

xml 复制代码
<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()函数

javascript 复制代码
_useCssVars((_ctx) => ({
  "5d92a9f9-color": color.value
}));

能不能猜到这个函数的作用是什么?如果不能,接着看下面一张图👇

这张图是组件的style部分编译之后的产物,可以看到

css 复制代码
.day_item {
    color: v-bind(color);
}

编译成了

css 复制代码
"._day_item_1oe25_1 {\n  color: var(--5d92a9f9-color);\n}"

也就是说我们使用的v-bind最终也是编译成了原生CSS中var函数,原理也是使用CSS的自定义变量

但是这里只有使用,并没看到css变量定义的地方🤔,现在能够猜测到_useCssVars()函数的作用是什么吗?大概率就是用来生成css自定义变量了。

接下来我们可以到源码中进行验证:

源码验证

  1. 找到源码中的doCompileStyle函数,打上断点,然后就可以启动debug模式了
  1. 接着往下走你会看到一个shortId变量,它此时的值是什么呢?

是不是有点眼熟,没错它就是后面会出现在CSS变量前面的那一串hash

  1. 再接着往下走,我们可以看到postcss插件中添加了一个cssVarsPlugin插件

这个插件的作用大家是不是已经猜到是干嘛的了,接着往下走

  1. 在cssVarsPlugin这个方法中再加一个断点

可以看到此时进来的decl参数是:color: v-bind(color)

熟悉postcss的同学应该能知道decl是什么意思,它表示的是css转化为AST后的一个节点类型

ini 复制代码
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节点上的

这个突破点在我们上面猜测流程的第一张图,里面有这样一段代码:

javascript 复制代码
_useCssVars((_ctx) => ({
  "5d92a9f9-color": color.value
}));

很明显,它就是用来生成CSS变量

  1. 接下来我们可以在源码中找到这个函数,并打上断点

在源码中搜索_useCssVars,你会发现什么也搜不到,这时我们可以尝试去掉_仔进行搜索,你会发现有这样一段代码:

ini 复制代码
const CSS_VARS_HELPER = `useCssVars`;

很明显,后面在源码中我们只需要搜索CSS_VARS_HELPER就可以,找到以下代码,打上断点,刷新页面

我们会发现这一段其实就是生成了我们上面那一段代码:

javascript 复制代码
_useCssVars((_ctx) => ({
  "5d92a9f9-color": color.value
}));

走到这里你会发现好像走不下去了,没有下一步了,因为最终我们看到的编译后的代码就是这个,具体是怎么把style中使用的变量转换成CSS变量并设置在对应dom节点上的这个并不是在编译时处理的。

想搞清楚这个我们还得在运行时打断点调试(这里换成了火狐浏览器进行断点调试,不要问为什么,问就是断点调试比谷歌好用)

接着往下走,会来到setVars方法这里

从方法名我们一眼就能看出它就是用来设置CSS变量的!

再往下走setVars -> setVarsOnVNode -> setVarsOnNode

在这里最终会调用setProperty方法来设置css变量。

到这里整个流程就结束了!

作为程序员,持续学习和充电非常重要,作为开发者,我们需要保持好奇心和学习热情,不断探索新的技术,只有这样,我们才能在这个快速发展的时代中立于不败之地。低代码也是一个值得我们深入探索的领域,让我们拭目以待,它将给前端世界带来怎样的变革,推荐一个低代码工具。

应用地址:www.jnpfsoft.com

开发语言:Java/.net

这是一个基于Flowable引擎(支持java、.NET),已支持MySQL、SqlServer、Oracle、PostgreSQL、DM(达梦)、 KingbaseES(人大金仓)6个数据库,支持私有化部署,前后端封装了上千个常用类,方便扩展,框架集成了表单、报表、图表、大屏等各种常用的 Demo 方便直接使用。

至少包含表单建模、流程设计、报表可视化、代码生成器、系统管理、前端 UI 等组件,这种情况下我们避免了重复造轮子,已内置大量的成熟组件,选择合适的组件进行集成或二次开发复杂功能,即可自主开发一个属于自己的应用系统。

相关推荐
半糖11228 分钟前
将本地项目提交到远程仓库
前端·vue.js
web150850966414 小时前
【React&前端】大屏适配解决方案&从框架结构到实现(超详细)(附代码)
前端·react.js·前端框架
理想不理想v4 小时前
前端项目性能优化(详细)
前端·性能优化
CodeToGym4 小时前
使用 Vite 和 Redux Toolkit 创建 React 项目
前端·javascript·react.js·redux
Cachel wood5 小时前
Vue.js前端框架教程8:Vue消息提示ElMessage和ElMessageBox
linux·前端·javascript·vue.js·前端框架·ecmascript
桃园码工7 小时前
4_使用 HTML5 Canvas API (3) --[HTML5 API 学习之旅]
前端·html5·canvas
桃园码工7 小时前
9_HTML5 SVG (5) --[HTML5 API 学习之旅]
前端·html5·svg
人才程序员7 小时前
QML z轴(z-order)前后层级
c语言·前端·c++·qt·软件工程·用户界面·界面
m0_548514777 小时前
前端三大主流框架:React、Vue、Angular
前端·vue.js·react.js
m0_748232398 小时前
单页面应用 (SPA):现代 Web 开发的全新视角
前端