Vue3中setup函数

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 是组件的一个选项,是一个函数
它接收两个参数:propscontext,最后 return 一个对象,对象里的属性会暴露给模板组件的 this

export default {
  setup(props, context) {
    const count = ref(0)
    function add() {
      count.value++
    }
    return { count, add }
  }
}

模板里就可以用 countadd;在选项式的 methodsmounted 里也可以通过 this.countthis.add 访问(因为 return 出来的会合并到组件实例上)。


二、setup 何时执行?

2.1 执行时机

setup 在组件创建时执行,且只执行一次
执行顺序在 beforeCreate 之前(或者说,在“创建”阶段最早执行),此时:

  • datacomputedmethods 等选项还没有被初始化;
  • this不可用(组件实例尚未完全创建好)。

所以:在 setup 里不能使用 this,要用参数返回值来和模板、其它选项配合。

2.2 和 created 的关系

setup 执行完之后,组件会继续走“创建”流程,这时 datamethods 等才可用。
可以粗略理解为:setup 的职责 ≈ 在“created 之前”把组合式 API 的逻辑写好,并把要暴露的 return 出去
原来在 created 里写的“不依赖 DOM 的初始化”,在组合式写法里可以写在 setup 顶层onMounted 里。


三、setup 的两个参数

3.1 第一个参数:props

props 是一个对象,里面是父组件传入的所有 prop,且是响应式的。
不要对 props解构,否则会失去响应式;需要解构时用 toRefscomputed

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 选项是对应的:选项里声明了 titlecount,setup 里就能从 props.titleprops.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 是一个普通对象,包含三个常用属性:attrsslotsemit
不是响应式的,可以安全解构。

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

slots 里是插槽内容
例如 slots.default 是默认插槽的函数,slots.header 是具名插槽 header 的函数。
里更多用 useSlots();在 setup 函数里用 context.slots 即可。

setup(props, { slots }) {
  const defaultSlot = slots.default
  const headerSlot = slots.header
  return { defaultSlot, headerSlot }
}

一般直接在模板里用 ,不需要在 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 }
  }
}

父组件:


四、setup 的返回值(return)

4.1 必须返回一个对象

setup 必须 return 一个对象
这个对象里的属性会:

  • 暴露给模板使用(模板里直接写变量名);
  • 合并到组件实例上,在 this.xxxmethodsmounted 等里也能访问。
setup() {
  const count = ref(0)
  const name = ref('')
  function add() {
    count.value++
  }
  return {
    count,
    name,
    add
  }
}

模板里:{{ count }}@click=”add”
return {} 或不 return,模板就拿不到这些变量。

4.2 返回什么,模板就能用什么

  • refreactivecomputed 等都可以 return,模板里会自动解包 ref。
  • 方法直接 return 函数即可。
  • 不 return 的变量只在 setup 内部有效,模板和 this 都访问不到。
setup() {
  const count = ref(0)
  const hidden = ref(0)
  function add() {
    count.value++
  }
  return { count, add }
}

模板里只能用 countaddhidden 没有 return,模板不能用。


五、setup 里没有 this

5.1 为什么没有?

setup 执行时,组件实例还没完全创建好,所以 thisundefined 或不能依赖。
因此:在 setup 里不要用 this,所有需要的数据、方法都通过参数(props、context)自己定义的变量 + return 来提供。

5.2 需要“组件实例”时怎么办?

  • props → 用第一个参数 props
  • emit → 用 context.emit
  • slots / attrs → 用 context.slotscontext.attrs
  • data / methods → 在 setup 里用 refreactive、普通函数,然后 return 出去,模板和 this 就都能用了。

六、setup 与选项式 API 一起用

6.1 可以同时存在

组件里可以既有 setup,又有 data、methods、mounted 等。
setup执行,它 return 出来的内容会与 datamethods合并到组件实例上;若同名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 暴露的

mountedmethods 里可以通过 this.xxx 访问 setup return 出来的内容:

export default {
  setup() {
    const count = ref(0)
    return { count }
  },
  mounted() {
    console.log(this.count)
  }
}

七、setup 与 的关系

7.1 是语法糖

会被编译成 setup 函数
里面的代码相当于放在 setup 函数体里,顶层的变量、函数、import 的组件相当于被自动 return 出去,所以模板都能用。

  • 不需要手写 setup(props, context) { … return { … } }
  • propsdefinePropsemitdefineEmitsattrs/slotsuseAttrs()useSlots()

7.2 对比一览

内容 setup 函数
参数 props setup(props) defineProps()
参数 context setup(props, context) defineEmits()、useAttrs()、useSlots()
返回值 必须 return 对象 无需 return,顶层自动暴露
this 不可用 不可用

实际开发中,新组件推荐用 ,写法更简单;需要理解“组合式入口”或维护老代码时,再回头看 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 的属性和 datamethods 同名时,以 setup 的 return 为准。


十一、setup 函数速查表

内容 说明
执行时机 组件创建时、只执行一次,在 beforeCreate 之前
第一个参数 props,响应式,不要解构(或用 toRefs)
第二个参数 context:attrs、slots、emit,可解构
返回值 必须 return 对象,其属性暴露给模板和 this
this 不可用
是语法糖,无需手写 setup 和 return

十二、学习建议

  1. 先理解:setup 是组合式 API 的入口return 什么模板就用什么没有 this
  2. 记牢两个参数:props(只读、不解构)和 context(attrs、slots、emit)。
  3. 新项目优先用 ;遇到“setup 函数”写法时,能看懂并知道和 的对应关系即可。

把本文档里的“带 props、emit 的 setup”在项目里写一遍,再改成 对比一下,会掌握得更牢。祝你学习顺利。

发表评论