Vue 3 中 setup 函数完全指南
本文档从零开始讲解 Vue 3 的 setup 函数:它是什么、何时执行、能接收哪些参数、要返回什么、为什么没有 this,以及和 的关系,配有大量示例,适合新手系统学习。
一、setup 函数是什么?
1.1 为什么要有 setup?
在 Vue 3 里,组合式 API(ref、reactive、computed、watch、生命周期 onMounted 等)需要一个统一的地方来写。
这个“统一的地方”就是 setup 函数:
你在 setup 里定义数据、方法、生命周期,然后通过 return 把需要给模板用的东西暴露出去,模板和组件的 this 就能访问到。
后来 Vue 又提供了 语法糖:不用手写 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 }
}
模板里可以写 slots 里是插槽内容。 一般直接在模板里用 、,不需要在 setup 里把 slots return 出去;需要“根据有没有插槽做逻辑”时才会在 setup 里读 slots。 emit 用来触发自定义事件,和选项里的 emits 对应。 父组件:。 setup 必须 return 一个对象。 模板里:{{ count }}、@click=”add”。 模板里只能用 count、add;hidden 没有 return,模板不能用。 setup 执行时,组件实例还没完全创建好,所以 this 是 undefined 或不能依赖。 组件里可以既有 setup,又有 data、methods、mounted 等。 mounted、methods 里可以通过 this.xxx 访问 setup return 出来的内容: 会被编译成 setup 函数: 实际开发中,新组件推荐用 ,写法更简单;需要理解“组合式入口”或维护老代码时,再回头看 setup 函数的写法。 setup 可以是 async 函数,但: 一般不推荐把 setup 写成 async,异步逻辑放在 onMounted 里或组合式函数里更清晰。 这样 data 要等请求完才有,组件在请求期间可能表现异常;更稳妥的是 return { data: ref(null) },在 onMounted 里请求并赋值。 const { title } = props 会丢响应式;要用 toRefs(props) 再解构。 在 setup 里定义了变量、方法,若不 return,模板和 this 都访问不到。 context 不是响应式的,可以写 const { attrs, slots, emit } = context。 return 的属性和 data、methods 同名时,以 setup 的 return 为准。 把本文档里的“带 props、emit 的 setup”在项目里写一遍,再改成 对比一下,会掌握得更牢。祝你学习顺利。slots
例如 slots.default 是默认插槽的函数,slots.header 是具名插槽 header 的函数。
在 里更多用 useSlots();在 setup 函数里用 context.slots 即可。setup(props, { slots }) {
const defaultSlot = slots.default
const headerSlot = slots.header
return { defaultSlot, headerSlot }
}emit
调用 emit(‘事件名’, 参数),父组件就能在 @事件名 里收到。export default {
emits: ['update', 'close'],
setup(props, { emit }) {
function handleClick() {
emit('update', 1)
}
function close() {
emit('close')
}
return { handleClick, close }
}
}
四、setup 的返回值(return)
4.1 必须返回一个对象
这个对象里的属性会:
setup() {
const count = ref(0)
const name = ref('')
function add() {
count.value++
}
return {
count,
name,
add
}
}
若 return {} 或不 return,模板就拿不到这些变量。4.2 返回什么,模板就能用什么
setup() {
const count = ref(0)
const hidden = ref(0)
function add() {
count.value++
}
return { count, add }
}
五、setup 里没有 this
5.1 为什么没有?
因此:在 setup 里不要用 this,所有需要的数据、方法都通过参数(props、context)和自己定义的变量 + return 来提供。5.2 需要“组件实例”时怎么办?
六、setup 与选项式 API 一起用
6.1 可以同时存在
setup 会先执行,它 return 出来的内容会与 data、methods 等合并到组件实例上;若同名,setup 的返回值优先。export default {
data() {
return { count: 0 }
},
setup() {
const name = ref('')
return { name }
}
}
6.2 在选项里用 setup 暴露的
export default {
setup() {
const count = ref(0)
return { count }
},
mounted() {
console.log(this.count)
}
}
七、setup 与 的关系
7.1 是语法糖
里面的代码相当于放在 setup 函数体里,顶层的变量、函数、import 的组件相当于被自动 return 出去,所以模板都能用。
7.2 对比一览
内容
setup 函数
参数 props
setup(props)
defineProps()
参数 context
setup(props, context)
defineEmits()、useAttrs()、useSlots()
返回值
必须 return 对象
无需 return,顶层自动暴露
this
不可用
不可用
八、完整示例
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(了解即可)
async setup() {
const data = await fetchData()
return { data }
}
十、常见问题与注意点
10.1 props 不要解构
10.2 必须 return 才能在模板里用
10.3 context 可解构
10.4 和选项同名时 setup 优先
十一、setup 函数速查表
内容
说明
执行时机
组件创建时、只执行一次,在 beforeCreate 之前
第一个参数
props,响应式,不要解构(或用 toRefs)
第二个参数
context:attrs、slots、emit,可解构
返回值
必须 return 对象,其属性暴露给模板和 this
this
不可用
与
是语法糖,无需手写 setup 和 return
十二、学习建议