setup 与 ref / reactive
约 856 字大约 3 分钟
布欧-Lewyon
2026-05-15
首页 › Vue › Composition API › setup 与 ref / reactive
Composition API 是 Vue 3 最核心的升级,通过 setup 函数将组件的逻辑按功能组织,而非像 Options API 那样按选项(data/methods/computed)分散。
setup 函数
基本形式
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
function increment() {
count.value++
}
// 返回的数据和方法可在模板中直接使用
return { count, increment }
}
}
</script>
<template>
<p>{{ count }}</p>
<button @click="increment">+1</button>
</template>setup 执行时机
- 在
beforeCreate之前执行(组件实例尚未创建) - 此时
this为undefined(不可访问) - 接收两个参数:
props、context
export default {
props: { title: String },
setup(props, context) {
// props 是响应式的(不要解构,会丢失响应性)
console.log(props.title)
// context 包含:attrs / slots / emit / expose
console.log(context.attrs) // 透传属性
console.log(context.slots) // 插槽
context.emit('update', val) // 触发事件
context.expose({ publicMethod }) // 暴露给父组件 ref
}
}<script setup>
Vue 3.2+ 引入的编译语法糖——推荐。
<script setup>
import { ref, computed } from 'vue'
// 顶层的绑定自动暴露给模板
const count = ref(0)
const double = computed(() => count.value * 2)
function increment() {
count.value++
}
</script>
<template>
<p>{{ count }} × 2 = {{ double }}</p>
<button @click="increment">+1</button>
</template>优势:
- 更少的样板代码(无需
return) - 更好的 TypeScript 类型推导
- 引入的组件自动注册
props/emit通过编译宏获取
ref
ref 将任意值包装为响应式引用:
<script setup>
import { ref } from 'vue'
// 基本类型
const count = ref(0) // { value: 0 }
const message = ref('hello') // { value: 'hello' }
const isDone = ref(false) // { value: false }
// 对象(内部自动转为 reactive)
const user = ref({
name: 'Alice',
age: 25
})
// 增删改查
function mutate() {
count.value++ // 通过 .value 读写
user.value.age = 26 // 对象属性自动响应式
user.value.email = 'a@b.com' // ✅ 新增属性也响应式
}
</script>
<template>
<!-- 模板中自动解包,不需要 .value -->
<p>{{ count }}</p>
<p>{{ user.name }} - {{ user.age }}</p>
</template>重要:在
<script setup>中操作 ref 需要.value,但在模板中会自动解包(unwrapping)。
reactive
reactive 返回对象的深层响应式代理:
<script setup>
import { reactive } from 'vue'
const state = reactive({
count: 0,
user: { name: 'Bob', nested: { value: 1 } }
})
function mutate() {
state.count++ // 不需要 .value
state.user.name = 'Alice'
state.user.nested.value = 2 // 深层响应式
}
</script>ref vs reactive
| 对比 | ref | reactive |
|---|---|---|
| 适用类型 | 任意类型(基本类型 + 对象) | 仅对象/数组/Map/Set |
| 访问方式 | .value(模板自动解包) | 直接 .xxx |
| 重新赋值 | ✅ count.value = 1 | ❌ 不能直接替换整个对象(会丢失响应式) |
| 解构 | 安全 const { count } = useXXX() | 丢失响应式,需 toRefs |
| 深层响应 | ✅ | ✅ |
// reactive 的错误用法
let state = reactive({ count: 0 })
state = reactive({ count: 1 }) // ❌ 丢失响应式
// 正确做法
const state = ref({ count: 0 })
state.value = { count: 1 } // ✅ ref 支持重新赋值toRef / toRefs
将 reactive 对象的属性转为 ref,保持响应性:
<script setup>
import { reactive, toRefs, toRef } from 'vue'
const state = reactive({
name: 'Alice',
age: 25
})
// ✅ 解构后保持响应式
const { name, age } = toRefs(state)
// ✅ 单个属性
const nameRef = toRef(state, 'name')
// 修改
name.value = 'Bob' // 同时更新 state.name
</script>ref 原理简析
function ref(value) {
if (isRef(value)) return value
return new RefImpl(value)
}
class RefImpl {
constructor(value) {
this._value = toReactive(value) // 对象 → reactive
this.__v_isRef = true
}
get value() {
track(this, 'value') // 依赖收集
return this._value
}
set value(newVal) {
this._value = toReactive(newVal)
trigger(this, 'value') // 触发更新
}
}小结
| API | 用途 |
|---|---|
ref | 任意类型响应式,.value 读写 |
reactive | 对象深层响应式,直接属性读写 |
toRefs | reactive 解构后保持响应式 |
<script setup> | 推荐方式,顶层绑定自动暴露 |
