跳到主要内容

创建日期:2026-09-08 | 最近更新:2026-09-08 基于 Vue 3.5.42;API 与行为以 cn.vuejs.org 为准。

Composition API 与 script setup:把「选项」变成「函数」

一句话:Composition API 不是「另一种写 state 的方式」,而是把 Vue 2 里散落在 data/computed/methods/watch 各栏的同一份逻辑,收拢到一个函数上下文里,让「组件逻辑」能像普通函数一样抽取、复用、组合<script setup> 只是它的语法糖——让你少写 return

1. ref vs reactive:先记住这三条

refreactive
适合基本类型、需要整对象替换的场景深层对象/嵌套状态
取值count.value(模板里自动解包,不用写 .valuestate.x(无 .value
特性传参/解构不丢响应性解构会丢(要 toRefs

给 Vue2 熟手的直觉

// data() 时代的字段,绝大多数换成 ref:
const count = ref(0)
count.value++

// 一整坨「配置对象」这类,才用 reactive:
const form = reactive({ name: '', age: 0 })
form.name = 'Lin'
  • 模板里 ref 自动解包<p>{{ count }}</p> 不用写 count.value(只有顶层 ref 会解包;塞进 reactive 的嵌套 ref 也会解包,见篇 3)。
  • reactive 对象解构会断const { name } = form 拿到的不是响应式。要解构且保留响应,用 toRefs(form)
  • ref 也能装对象ref({ a: 1 }) 内部会用 reactive 包裹 valueobj.value.a 深层也响应。所以不确定时用 ref 准没错

2. script setup:少写一半样板

Vue 2 里你写 <script> export default { ... } </script>;Vue 3 主流是:

<script setup>
import { ref, computed } from 'vue'

// 顶层声明的都是「模板可见」的,不用 return
const count = ref(0)
const double = computed(() => count.value * 2)
</script>

<template>
<button @click="count++">{{ count }} × 2 = {{ double }}</button>
</template>
  • 模板里能用顶层变量/函数import 进来的组件/组合式函数也直接可用,无需注册。
  • 每个 SFC 只能有一个 <script setup>(可与普通 <script> 并存做极少量的 Options 兜底,但不推荐)。
  • <script setup> 里的组件是默认关闭的——props/attrs 不会自动透传给你在模板里自由访问,外部想调组件内部方法要显式 defineExpose(见 §6)。

3. computed 与 watch 家族

computed:是「懒 + 缓存」的派生状态

const search = ref('')
const results = computed(() => filter(list.value, search.value)) // 依赖变了才重算

模板、别的 computed、watch 都能读 results.value能 computed 就别手动 watch + 赋值——后者容易造成「多处同步状态」的意大利面。

watch 家族的三个选择

watch(source, (val, old) => {}, { deep: true }) // 精确监听:单个 ref / getter / 数组
watch([a, b], ([av, bv], [aov, bov]) => {}) // 多个来源
watchEffect(() => { doWith(state.x) }) // 自动收集:回调里读到啥就盯啥
watchPostEffect(() => { /* DOM 更新后 */ }) // 等同 old flush:'post'
Vue2Vue3
watch: { x() {} }(默认浅)watch(x, cb);对象要 { deep: true }
immediate: true加选项 { immediate: true } 或直接用 watchEffect
在组件销毁自动停组件卸载自动停;跨组件/全局场景用 watchEffect 拿的 stop + onScopeDispose(篇 5)

提醒:Vue2 里 watch 对象默认是「引用变化才触发」;Vue3 里 watch 默认不 deepreactive 对象除外——直接 watch 一个 reactive 对象时它深),要深监听显式 deep: true

生命周期对照

Vue 2Vue 3 Composition
created直接在 setup 顶层写(同步执行即此时机)
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeDestroyonBeforeUnmount
destroyedonUnmounted
errorCapturedonErrorCaptured
——onActivated/onDeactivated(keep-alive)

<script setup> 里这些 onXxx 不用从 options 里翻,直接 import { onMounted } from 'vue' 后调用。

4. 组件通信的新形状

props 与 emits:编译宏

<script setup>
const props = defineProps({
title: { type: String, required: true },
init: { type: Number, default: 0 },
})
// 用带默认值的写法更省:defineProps({ init: { default: 0 } }) → props.init
const emit = defineEmits(['update', 'save'])
</script>
  • defineProps / defineEmits编译宏:不需要 import,写在 <script setup> 顶层。
  • TS 项目里更常见类型版:defineProps<{ title: string; init?: number }>()(篇 5)。
  • 旧的 props: { … } + this.$emit 在 Options 写法里依然可用;但新组件用宏。

defineModel:v-model 的时代终于不用手搓了

Vue2 里给组件做 v-modelvalue prop + this.$emit('input');Vue3 早期要 modelValue + update:modelValuedefineModel(3.4+)一键收编

<!-- 父:<MyInput v-model="keyword" /> -->
<script setup>
const model = defineModel({ type: String, required: true })
// model 是个 ref:改它就相当于触发 update:modelValue
</script>
<template><input v-model="model" /></template>

多个 model:const a = defineModel('a'); const b = defineModel('b') → 父可写 v-model:av-model:b。加修饰符也一样(defineModel 会带 modifiers)。

暴露内部方法:defineExpose

<script setup> 组件默认「关着门」——父组件 ref 到它时拿不到内部函数。要开:

<script setup>
const doReset = () => { /* … */ }
defineExpose({ doReset }) // 父组件 ref 就能调用
</script>

attrs / slots 访问

import { useAttrs, useSlots } from 'vue'
const attrs = useAttrs() // 等价 this.$attrs(Vue3 里含监听器)
const slots = useSlots() // 等价 this.$slots

5. provide / inject:跨层传「活」的东西

Vue2 你见过 provide/inject 但不常用来传响应式。Vue3 里它俩是组合式跨层共享的主力(替代部分「到处用 Vuex」的冲动):

// 祖先组件
import { ref, provide } from 'vue'
const theme = ref('light')
provide('theme', theme) // 传的是 ref,后代改主题全家响应
provide('setTheme', v => theme.value = v)

// 任意后代组件
const theme = inject('theme')
  • ref 而非原始值,才能「改一处、处处响应」;
  • 组合式函数 + provide/inject 即可实现「无状态库的全局 store」——Pinia 本质就是这个模式 + 性能优化(篇 5)。

6. 什么时候还想用 Options

Vue 3 允许混用:一个 SFC 里可以 export default { setup() {...}, data() {...} }(Composition 为主体、少量 Options 兜底),但对新代码不推荐混。Options 唯一还有存在感的场景是老代码渐进迁移期——先不动的组件保持 Options,新增逻辑用 setup() 选项一点一点迁。

7. 常见的「从 Vue2 惯性」坑

  1. 忘了 .valuecount++ 改成 count.value++;模板里没事,<script>/JS 里忘写 .value 是最常见编译/运行 bug。
  2. 解构 reactive 丢响应:别 const { a } = reactive({...}),要 toRefs 或直接对 ref。
  3. watch 默认不深:深层对象变化没触发,先怀疑没写 deep: true
  4. this 当真:Composition 里没有组件 this;要拿实例相关能力用 getCurrentInstance()(尽量少用)。
  5. <script setup> 外用了宏defineProps 只在 <script setup> 顶层有效,普通 <script> 里要用 props 选项。

关联