🧠 Vue 编译器的表达式处理:transformExpression 通俗讲解

在 Vue 模板编译阶段,有一个特别重要的过程:把模板里的表达式转成可执行的 JS 代码

比如模板里写:

css 复制代码
<div>{{ count + 1 }}</div>

Vue 编译器会生成类似的渲染函数:

kotlin 复制代码
return _ctx.count + 1

你写的只是 count,但编译器偷偷帮你加上了 _ctx.

这一步加前缀 _ctx.$props. 的过程,就叫 表达式重写(expression transform)

而负责干这事的,就是这个文件里的 transformExpression


一、transformExpression 是干嘛的?

它的任务分两部分:

  1. 找到表达式节点(如 {{ ... }}v-bind 等)
  2. 调用 processExpression 来改写里面的代码

简单理解就是:

它扫描模板 AST(语法树),遇到表达式就去"加工"。


二、举个例子

假设你写了这个组件:

xml 复制代码
<template>
  <div>{{ count + 1 }}</div>
</template>

<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>

模板里的 {{ count + 1 }} 看似简单,实际上 Vue 内部会:

  1. 用 Babel 把它解析成一个 JS 语法树;
  2. 找出每一个标识符(count);
  3. 判断这个变量是哪来的;
  4. 把它重写成正确的访问形式,比如 count.value

最后渲染函数就变成:

kotlin 复制代码
return count.value + 1

三、processExpression 是整个逻辑的核心

源码中 processExpression() 是整个文件的"大脑",

它会:

  1. 用 Babel 解析字符串表达式;
  2. 找出所有变量;
  3. 判断是不是 refpropssetup 变量;
  4. 给变量加上前缀或 .value

📍1. 解析表达式

ini 复制代码
ast = parseExpression(source, { plugins: context.expressionPlugins })

Vue 用 Babel 的 @babel/parser 来把字符串 count + 1 转成一棵树,

这样它就能知道哪里是变量、哪里是操作符、哪里是函数。


📍2. 遍历变量(标识符)

javascript 复制代码
walkIdentifiers(ast, (node, parent, _, isReferenced, isLocal) => {
  // ...
})

这一步相当于"扫描"所有变量,比如:

复制代码
count + msg

会找到两个标识符:countmsg


📍3. 判断变量来源(bindingMetadata)

Vue 编译时会记录每个变量的"来源":

来源类别 标识 含义 重写结果
setup 里的 ref SETUP_REF 响应式引用 count.value
setup 常量 SETUP_CONST 普通变量 count
props PROPS 传入属性 __props.xxx
data/methods 其他 实例上下文 _ctx.xxx

📍4. 真正的改写逻辑(rewriteIdentifier

看这一段是关键👇:

typescript 复制代码
if (type === BindingTypes.SETUP_REF) {
  return `${raw}.value`
} else if (type === BindingTypes.PROPS) {
  return genPropsAccessExp(raw) // => __props.xxx
} else if (type && type.startsWith('setup')) {
  return `$setup.${raw}`
} else {
  return `_ctx.${raw}`
}

意思就是:

  • 如果这个变量是个 ref → 加 .value
  • 如果是 props → 改成 __props.xxx
  • 如果是 setup 变量 → 改成 $setup.xxx
  • 其他情况 → 加 _ctx.

📍5. 拼回去生成最终表达式

举个例子:

复制代码
{{ count + msg }}

经过改写后:

复制代码
count.value + _ctx.msg

Vue 把这整个表达式重新拼接成一个"复合表达式(CompoundExpressionNode)",

这样后续生成渲染函数时就能直接插入。


四、为什么不直接用变量名?

假设你模板里写了 {{ count }}

但渲染函数最终运行在一个 JS 环境中,如果没有前缀,它会变成:

kotlin 复制代码
return count // ❌ JS 会报错:count is not defined

因为这个 count 不是全局变量,而是在组件内部。

Vue 就要帮你加上 _ctx..value,告诉 JS 去正确的对象上取值。


五、来看看几种常见的改写结果

模板写法 来源 编译结果
{{ msg }} data _ctx.msg
{{ title }} props __props.title
{{ count }} setup ref count.value
{{ name }} setup const name
{{ count + msg }} 混合 count.value + _ctx.msg

是不是就清楚多了?

Vue 的目标是让模板看起来简单,但编译后保证代码能正常访问正确的变量。


六、总结一句话

transformExpression 的工作就是:

找出模板中的每一个变量,

看它属于谁(setup / props / data / 全局),

然后在编译时改写成能正确访问的 JavaScript 代码。

这样你写模板时不用管上下文,Vue 会帮你在编译阶段自动"补全"访问路径。


✅ 小结(一句话复盘)

  • 模板 → 编译器扫描表达式
  • Babel 解析 → 找出变量
  • 判断变量来源 → 加前缀 / 加 .value
  • 重新拼成新表达式
  • 最终生成正确的渲染函数代码

本文部分内容借助 AI 辅助生成,并由作者整理审核。

相关推荐
红尘散仙33 分钟前
想写一个像样的终端 App?试试把 React 的开发体验搬进 Rust TUI
前端·rust
袋鼠云数栈UED团队1 小时前
一套 Spec-First 的 AI 编程工作流
前端·人工智能
袋鼠云数栈前端1 小时前
一套 Spec-First 的 AI 编程工作流
前端·ai+
angerdream1 小时前
Android手把手编写儿童手机远程监控App之vue3 路由守卫
前端
不服老的小黑哥1 小时前
AI规范驱动编程-harness工程项目实战
前端
vivo互联网技术1 小时前
从 Web 到桌面:基于 Tauri 2.0 + Vue 3 打造 vivo 线下门店「大头贴」拍照体验系统
前端·rust
光影少年2 小时前
React 合成事件机制、和原生事件区别、事件冒泡阻止
前端·react.js·掘金·金石计划
没有鸡汤吃不下饭2 小时前
告别手动对接口:我用 OpenAPI JSON 做了一个前端接口同步 Skill
前端·ai编程
空栈独白2 小时前
NestJS实战-前后端联调
前端
米饭同学i2 小时前
浏览器记住密码导致忘记密码页面输入框回显错乱?看这篇就够了
前端