🧠 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 辅助生成,并由作者整理审核。

相关推荐
vipbic35 分钟前
用 Turborepo 打造 Strapi 插件开发的极速全栈体验
前端·javascript
天涯学馆35 分钟前
为什么 JavaScript 可以单线程却能处理异步?
前端·javascript
Henry_Lau6171 小时前
主流IDE常用快捷键对照
前端·css·ide
陶甜也1 小时前
使用Blender进行现代建筑3D建模:前端开发者的跨界探索
前端·3d·blender
我命由我123452 小时前
VSCode - Prettier 配置格式化的单行长度
开发语言·前端·ide·vscode·前端框架·编辑器·学习方法
HashTang2 小时前
【AI 编程实战】第 4 篇:一次完美 vs 五轮对话 - UnoCSS 配置的正确姿势
前端·uni-app·ai编程
JIngJaneIL2 小时前
基于java + vue校园快递物流管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js
asdfg12589632 小时前
JS中的闭包应用
开发语言·前端·javascript
kirk_wang2 小时前
Flutter 导航锁踩坑实录:从断言失败到类型转换异常
前端·javascript·flutter