Vue 3 中 setup 函数完全指南
本文档从零开始讲解 Vue 3 的 setup 函数:它是什么、何时执行、能接收哪些参数、要返回什么、为什么没有 this,以及和 <script setup> 的关系,配有大量示例,适合新手系统学习。
一、setup 函数是什么?
1.1 为什么要有 setup?
在 Vue 3 里,组合式 API(ref、reactive、computed、watch、生命周期 onMounted 等)需要一个统一的地方来写。
这个“统一的地方”就是 setup 函数:
你在 setup 里定义数据、方法、生命周期,然后通过 return 把需要给模板用的东西暴露出去,模板和组件的 this 就能访问到。
后来 Vue 又提供了 <script setup> 语法糖:不用手写 setup 函数、不用 return,顶层变量自动暴露给模板。
但理解 setup 函数本身,有助于搞清“组合式 API 的入口”和“props、emit、slots 怎么拿”。
1.2 setup 长什么样?
setup 是组件的一个选项,是一个函数。
它接收两个参数:props 和 context,最后 return 一个对象,对象里的属性会暴露给模板和组件的 this。
export default {
setup(props, context) {
const count = ref(0)
function add() {
count.value++
}
return { count, add }
}
}
模板里就可以用 count 和 add;在选项式的 methods、mounted 里也可以通过 this.count、this.add 访问(因为 return 出来的会合并到组件实例上)。
二、setup 何时执行?
2.1 执行时机
setup 在组件创建时执行,且只执行一次。
执行顺序在 beforeCreate 之前(或者说,在“创建”阶段最早执行),此时:
- data、computed、methods 等选项还没有被初始化;
- this 还不可用(组件实例尚未完全创建好)。
所以:在 setup 里不能使用 this,要用参数和返回值来和模板、其它选项配合。
2.2 和 created 的关系
setup 执行完之后,组件会继续走“创建”流程,这时 data、methods 等才可用。
可以粗略理解为:setup 的职责 ≈ 在“created 之前”把组合式 API 的逻辑写好,并把要暴露的 return 出去。
原来在 created 里写的“不依赖 DOM 的初始化”,在组合式写法里可以写在 setup 顶层或 onMounted 里。
三、setup 的两个参数
3.1 第一个参数:props
props 是一个对象,里面是父组件传入的所有 prop,且是响应式的。
不要对 props 做解构,否则会失去响应式;需要解构时用 toRefs 或 computed。
export default {
props: {
title: String,
count: Number
},
setup(props) {
console.log(props.title)
console.log(props.count)
watch(() => props.count, (v) => {
console.log('count 变了', v)
})
return {}
}
}
- props 和选项里的 props 选项是对应的:选项里声明了 title、count,setup 里就能从 props.title、props.count 读到。
- 若在 setup 里需要“基于 props 的派生状态”,用 computed(() => props.xxx) 或 toRefs(props)。
错误示例:解构 props 会丢响应式
setup(props) {
const { title, count } = props
watch(() => count, () => {}) // count 不是响应式的,可能不生效
}
正确:需要“像变量一样用”时用 toRefs
import { toRefs } from 'vue'
setup(props) {
const { title, count } = toRefs(props)
watch(count, () => {}) // count 是 ref,响应式保留
return { title, count }
}
3.2 第二个参数:context(上下文)
context 是一个普通对象,包含三个常用属性:attrs、slots、emit。
它不是响应式的,可以安全解构。
setup(props, context) {
const { attrs, slots, emit } = context
return {}
}
attrs
attrs 里是父组件传下来的、但未在 props 里声明的属性(以及未在 emits 里声明的事件名对应的 onXxx)。
例如:父写了 :id="xxx",子没有在 props 里声明 id,那 attrs.id 就能拿到。
常用于把 attrs 绑定到内部某个元素上(v-bind="attrs"),做“属性透传”。
setup(props, { attrs }) {
console.log(attrs.class)
console.log(attrs.id)
return { attrs }
}
模板里可以写 <div v-bind="attrs">,把未声明的属性都绑到该元素上。
slots
slots 里是插槽内容。
例如 slots.default 是默认插槽的函数,slots.header 是具名插槽 header 的函数。
在 <script setup> 里更多用 useSlots();在 setup 函数里用 context.slots 即可。
setup(props, { slots }) {
const defaultSlot = slots.default
const headerSlot = slots.header
return { defaultSlot, headerSlot }
}
一般直接在模板里用 <slot>、<slot name="header">,不需要在 setup 里把 slots return 出去;需要“根据有没有插槽做逻辑”时才会在 setup 里读 slots。
emit
emit 用来触发自定义事件,和选项里的 emits 对应。
调用 emit(‘事件名’, 参数),父组件就能在 @事件名 里收到。
export default {
emits: ['update', 'close'],
setup(props, { emit }) {
function handleClick() {
emit('update', 1)
}
function close() {
emit('close')
}
return { handleClick, close }
}
}
父组件:<MyComp @update="onUpdate" @close="onClose" />。
四、setup 的返回值(return)
4.1 必须返回一个对象
setup 必须 return 一个对象。
这个对象里的属性会:
- 暴露给模板使用(模板里直接写变量名);
- 合并到组件实例上,在 this.xxx、methods、mounted 等里也能访问。
setup() {
const count = ref(0)
const name = ref('')
function add() {
count.value++
}
return {
count,
name,
add
}
}
模板里:{{ count }}、@click="add"。
若 return {} 或不 return,模板就拿不到这些变量。
4.2 返回什么,模板就能用什么
- ref、reactive、computed 等都可以 return,模板里会自动解包 ref。
- 方法直接 return 函数即可。
- 不 return 的变量只在 setup 内部有效,模板和 this 都访问不到。
setup() {
const count = ref(0)
const hidden = ref(0)
function add() {
count.value++
}
return { count, add }
}
模板里只能用 count、add;hidden 没有 return,模板不能用。
五、setup 里没有 this
5.1 为什么没有?
setup 执行时,组件实例还没完全创建好,所以 this 是 undefined 或不能依赖。
因此:在 setup 里不要用 this,所有需要的数据、方法都通过参数(props、context)和自己定义的变量 + return 来提供。
5.2 需要“组件实例”时怎么办?
- props → 用第一个参数 props。
- emit → 用 context.emit。
- slots / attrs → 用 context.slots、context.attrs。
- data / methods → 在 setup 里用 ref、reactive、普通函数,然后 return 出去,模板和 this 就都能用了。
六、setup 与选项式 API 一起用
6.1 可以同时存在
组件里可以既有 setup,又有 data、methods、mounted 等。
setup 会先执行,它 return 出来的内容会与 data、methods 等合并到组件实例上;若同名,setup 的返回值优先。
export default {
data() {
return { count: 0 }
},
setup() {
const name = ref('')
return { name }
}
}
- this.count 来自 data,this.name 来自 setup 的 return。
- 若 setup 里也 return 了 count,则 this.count 会用 setup 的(覆盖 data 的同名属性)。
6.2 在选项里用 setup 暴露的
mounted、methods 里可以通过 this.xxx 访问 setup return 出来的内容:
export default {
setup() {
const count = ref(0)
return { count }
},
mounted() {
console.log(this.count)
}
}
七、setup 与 <script setup> 的关系
7.1 <script setup> 是语法糖
<script setup> 会被编译成 setup 函数:
里面的代码相当于放在 setup 函数体里,顶层的变量、函数、import 的组件相当于被自动 return 出去,所以模板都能用。
- 不需要手写 setup(props, context) { … return { … } }。
- props 用 defineProps,emit 用 defineEmits,attrs/slots 用 useAttrs()、useSlots()。
7.2 对比一览
| 内容 | setup 函数 | <script setup> |
|---|---|---|
| 参数 props | setup(props) | defineProps() |
| 参数 context | setup(props, context) | defineEmits()、useAttrs()、useSlots() |
| 返回值 | 必须 return 对象 | 无需 return,顶层自动暴露 |
| this | 不可用 | 不可用 |
实际开发中,新组件推荐用 <script setup>,写法更简单;需要理解“组合式入口”或维护老代码时,再回头看 setup 函数的写法。
八、完整示例
8.1 只有 setup:数据 + 方法
// MyCounter.vue
export default {
setup() {
const count = ref(0)
function add() {
count.value++
}
return { count, add }
}
}
<template>
<p>{{ count }}</p>
<button @click="add">+1</button>
</template>
8.2 带 props 和 emit
export default {
props: {
title: String
},
emits: ['update'],
setup(props, { emit }) {
const count = ref(0)
function add() {
count.value++
emit('update', count.value)
}
return { count, add }
}
}
8.3 使用 toRefs(props)
import { ref, toRefs } from 'vue'
export default {
props: {
title: String,
count: Number
},
setup(props) {
const { title, count } = toRefs(props)
const double = computed(() => count.value * 2)
return { title, count, double }
}
}
九、async setup(了解即可)
setup 可以是 async 函数,但:
- 返回的是 Promise,组件不会自动等待这个 Promise;
- 若配合 Suspense,可以等 setup 的 Promise resolve 后再渲染子组件。
一般不推荐把 setup 写成 async,异步逻辑放在 onMounted 里或组合式函数里更清晰。
async setup() {
const data = await fetchData()
return { data }
}
这样 data 要等请求完才有,组件在请求期间可能表现异常;更稳妥的是 return { data: ref(null) },在 onMounted 里请求并赋值。
十、常见问题与注意点
10.1 props 不要解构
const { title } = props 会丢响应式;要用 toRefs(props) 再解构。
10.2 必须 return 才能在模板里用
在 setup 里定义了变量、方法,若不 return,模板和 this 都访问不到。
10.3 context 可解构
context 不是响应式的,可以写 const { attrs, slots, emit } = context。
10.4 和选项同名时 setup 优先
return 的属性和 data、methods 同名时,以 setup 的 return 为准。
十一、setup 函数速查表
| 内容 | 说明 |
|---|---|
| 执行时机 | 组件创建时、只执行一次,在 beforeCreate 之前 |
| 第一个参数 | props,响应式,不要解构(或用 toRefs) |
| 第二个参数 | context:attrs、slots、emit,可解构 |
| 返回值 | 必须 return 对象,其属性暴露给模板和 this |
| this | 不可用 |
| 与 <script setup> | <script setup> 是语法糖,无需手写 setup 和 return |
十二、学习建议
- 先理解:setup 是组合式 API 的入口,return 什么模板就用什么,没有 this。
- 记牢两个参数:props(只读、不解构)和 context(attrs、slots、emit)。
- 新项目优先用 <script setup>;遇到“setup 函数”写法时,能看懂并知道和 <script setup> 的对应关系即可。
把本文档里的“带 props、emit 的 setup”在项目里写一遍,再改成 <script setup> 对比一下,会掌握得更牢。祝你学习顺利。