一、这段代码是干什么的?
Vue 组件有两种写法:
| 类型 | 示例 | 特点 |
|---|---|---|
普通 <script> |
export default { data(){...}, props:{...} } |
传统写法 |
<script setup> |
顶层直接写 const count = ref(0) |
Vue3 新写法 |
而 Vue 编译器在处理 .vue 文件时,需要知道:
每个变量来自哪里?它是 props 吗?data 吗?methods 吗?
这段源码的作用就是:
👉 当我们用"普通写法"时,分析出每个变量的"来源类型"。
二、运行结果长什么样?
假设我们有个组件:
javascript
export default {
props: ['title'],
data() {
return { count: 0 }
},
methods: {
inc() { this.count++ }
}
}
经过这段分析函数后,会得到这样的结果对象:
yaml
{
title: 'props',
count: 'data',
inc: 'options',
__isScriptSetup: false
}
这就告诉 Vue:
title来自 props;count来自 data;inc是 methods;- 不是
<script setup>。
三、从头到尾一步步看源码逻辑
Step 1️⃣:找到 export default { ... }
arduino
export function analyzeScriptBindings(ast: Statement[]): BindingMetadata {
for (const node of ast) {
if (
node.type === 'ExportDefaultDeclaration' &&
node.declaration.type === 'ObjectExpression'
) {
return analyzeBindingsFromOptions(node.declaration)
}
}
return {}
}
🧠 意思:
-
AST 是整段脚本的语法树。
-
遍历每个语句,找到:
arduinoexport default { ... } -
然后调用
analyzeBindingsFromOptions()来分析里面的对象内容。
Step 2️⃣:创建结果对象并标记类型
php
const bindings: BindingMetadata = {}
Object.defineProperty(bindings, '__isScriptSetup', {
enumerable: false,
value: false,
})
📘 这一步干嘛?
- 初始化一个结果对象;
- 加一个隐藏属性
__isScriptSetup=false,告诉系统"这是普通 script"。
Step 3️⃣:逐个分析对象里的属性
例如:
javascript
export default {
props: ['foo'],
data() { return { msg: 'hi' } },
methods: { sayHi() {} }
}
程序就会循环每个属性(props、data、methods...),判断它是哪种类型。
Step 4️⃣:不同类型的属性,分别分析
(1) props 分析
vbnet
if (property.key.name === 'props') {
for (const key of getObjectOrArrayExpressionKeys(property.value)) {
bindings[key] = BindingTypes.PROPS
}
}
🧠 支持两种写法:
props: ['foo', 'bar']props: { foo: String }
结果:
css
{ foo: 'props', bar: 'props' }
(2) inject 分析
vbnet
else if (property.key.name === 'inject') {
for (const key of getObjectOrArrayExpressionKeys(property.value)) {
bindings[key] = BindingTypes.OPTIONS
}
}
对应:
vbnet
inject: ['token']
👉 结果 { token: 'options' }
(3) methods / computed 分析
python
else if (
property.value.type === 'ObjectExpression' &&
(property.key.name === 'computed' || property.key.name === 'methods')
)
📘 当 methods: { sayHi(){} } 或 computed: { total(){} } 时,
把每个函数名记录下来:
css
{ sayHi: 'options', total: 'options' }
(4) data / setup 分析
python
else if (
property.type === 'ObjectMethod' &&
(property.key.name === 'setup' || property.key.name === 'data')
)
这时要进入函数体里查找 return 的内容:
javascript
data() {
return { count: 0 }
}
setup() {
return { foo: ref(0) }
}
📘 分析结果:
- data 返回的变量 →
BindingTypes.DATA - setup 返回的变量 →
BindingTypes.SETUP_MAYBE_REF
四、辅助函数们(简化理解)
1️⃣ 获取对象的键名
css
function getObjectExpressionKeys(node) {
// 从 { foo: 1, bar: 2 } 中提取出 ['foo', 'bar']
}
2️⃣ 获取数组的键名
css
function getArrayExpressionKeys(node) {
// 从 ['foo', 'bar'] 中提取出 ['foo', 'bar']
}
3️⃣ 自动判断是对象还是数组
javascript
export function getObjectOrArrayExpressionKeys(value) {
// 根据类型选择上面的函数
}
五、整体运行逻辑图
markdown
AST语法树
↓
找到 export default {}
↓
进入 analyzeBindingsFromOptions()
↓
循环每个属性:
- props → PROPS
- inject → OPTIONS
- methods/computed → OPTIONS
- data → DATA
- setup → SETUP_MAYBE_REF
↓
返回 BindingMetadata
六、为什么这么做?
因为 Vue 在模板编译时,需要知道哪些名字是:
- 响应式变量(data、setup)
- 只读输入(props)
- 普通函数(methods)
这样模板里写的:
css
<p>{{ count }}</p>
才能被编译成正确的访问代码:
_ctx.count
或者:
_props.title
七、你可以怎么用它?
如果你想做:
- 自定义 Vue 编译工具;
- 分析
.vue文件中定义的变量; - 或者写一个 ESLint 规则来检测组件结构;
就可以直接复用这段逻辑,让它帮你快速"读懂" Vue 组件结构。
八、潜在问题
| 问题 | 说明 |
|---|---|
| 不支持动态 key | [foo]: value 这种会被忽略 |
| 不识别 TS 类型 | 如果写 props: { foo: String as PropType<number> } 不会处理 |
| 无法分析复杂 setup 返回逻辑 | 例如条件 return 不被识别 |
✅ 总结一句话
这段代码的作用就是让编译器"看懂"一个普通 Vue 组件里的变量来源,区分哪些是 props、data、methods、setup 返回的。
本文部分内容借助 AI 辅助生成,并由作者整理审核。