Vue 3 setup 深入解析与原理解析
一、核心要点速览
💡 核心考点
- 什么是 setup: 组合式 API (Composition API) 的入口点,在组件创建之前执行。
- <script setup>: Vue 3.2+ 引入的编译时语法糖,大幅减少了样板代码。
- 运行时原理:
setup()接收props和context参数,返回的对象会被暴露给模板。 - 执行时机: 处于
beforeCreate之前,此时无法访问this。
二、setup 的双重身份:编译时与运行时
在 Vue 3 中,我们通常有两种方式使用 setup。理解它们的区别是掌握 Vue 3 底层原理的关键。
1. 编译时语法糖:<script setup>
这是目前官方推荐的写法。它是一个编译时的转换过程。
- 核心作用:Vue Compiler 会将
<script setup>内部的代码包装到一个隐藏的setup()函数中。 - 自动暴露:在顶级作用域声明的所有变量、函数、导入的组件,都会被自动暴露给模板使用,无需显式
return。 - Compiler Macros:引入了
defineProps、defineEmits等编译器宏,它们在编译时会被替换为相应的运行时声明。
vue
<script setup>
import { ref } from 'vue'
import MyComponent from './MyComponent.vue' // 自动注册
const count = ref(0) // 自动暴露给模板
const increment = () => count.value++
// 编译器宏:不需要 import,编译时处理
const props = defineProps(['title'])
</script>
<template>
<button @click="increment">{{ title }}: {{ count }}</button>
<MyComponent />
</template>2. 运行时入口:setup() 函数
这是组合式 API 的基础。如果你不使用 <script setup>,就需要显式定义 setup() 函数。
- 接收参数:
props: 响应式的 props 对象。context: 一个包含attrs、slots、emit、expose的非响应式对象。
- 返回值:
- 如果返回一个对象:该对象的所有属性都会被合并到渲染上下文中。
- 如果返回一个渲染函数:可以直接用它来定义组件的输出(通常用于 JSX/TSX)。
javascript
export default {
props: ['title'],
setup(props, { emit }) {
const count = ref(0)
// 必须显式返回
return {
count,
increment: () => count.value++
}
}
}三、setup 与模板的关系:渲染上下文 (Rendering Context)
无论你使用哪种写法,最终 Vue 都会创建一个渲染上下文。
- 执行 setup:Vue 调用
setup()函数。 - 获取 Bindings:收集
setup返回的所有变量。 - 代理访问:模板编译生成的渲染函数中,对变量的访问(如
)会被代理到这个返回的对象上。
💡 关键区别:
- 在
setup()函数中,你需要通过count.value访问响应式数据。- 在模板中,Vue 会自动解包 (Unwrapping),所以你只需要写
。
四、高频面试题
1. setup 执行时机是在什么时候?为什么不能访问 this?
回答: setup 在 beforeCreate 生命周期钩子之前执行。由于此时组件实例(Internal Component Instance)尚未完全创建,Vue 故意没有绑定 this。这样做的目的是为了强制开发者使用组合式 API 的方式思考,避免 this 指向混乱。
2. <script setup> 相比普通 setup() 函数有哪些优势?
回答:
- 代码更简洁:无需显式返回对象,减少了样板代码。
- 更好的性能:由于模板直接可以使用 setup 中的变量,无需经过 Proxy 代理,编译出的代码效率更高。
- 更好的类型推导:对 TypeScript 支持更友好。
- 自动组件注册:导入的组件直接在模板可用。
3. setup 中的 props 是响应式的吗?解构 props 会发生什么?
回答: props 是响应式的。但是不能使用 ES6 解构(如 const { title } = props),因为解构会使变量丢失响应性。
- 解决:如果需要解构,应使用
toRefs或toRef(例如:const { title } = toRefs(props))。
4. defineProps 和 defineEmits 为什么不需要从 vue 导入?
回答: 因为它们是编译器宏 (Compiler Macros)。它们只在 <script setup> 中可用,Vue 编译器在扫描代码时会识别它们,并在编译阶段将其转换为运行时代码,所以不需要在运行时真正导入它们。
5. setup 内部如何获取插槽 (slots) 和自定义事件 (emit)?
回答:
- 在普通
setup(props, context)中,通过context.slots和context.emit获取。 - 在
<script setup>中,使用useSlots()和useEmits()(或defineEmits)获取。
五、总结
- <script setup> 是编译时的魔法,让代码更清爽 🪄
- setup() 是运行时的锚点,是逻辑组织的根基 ⚓
- 它们共同构成了 Vue 3 强大的组合式开发模式,让代码复用变得从未如此简单。