提出问题
直接 解构 reactive 对象,变量就不是响应式
<template>
姓名:{{name}}<br>
年龄:{{age}}<br>
性别:{{sex}}<br>
身高:{{height}}<br>
<button @click="addAge">年龄加1</button>
<button @click="modifyName">修改名字</button>
</template>
<script lang="ts" setup name='Student'>
import { reactive } from 'vue'
let student = reactive({
name: '李雷',
age: 20,
sex: '男',
height: 1.82
})
// ❌ 直接解构,name、age等 不是响应式
let { name, age, sex, height } = student
// 通过打印可知就是普通的值
console.log("name:",name)
console.log("age:",age)
console.log("sex:",sex)
function addAge() {
// 执行函数,页面的值不变化
age++
}
function modifyName() {
// 执行函数,页面的值不变化
name = '李大雷'
}
</script>
为什么要解构 reactive 对象
工程化项目里 解构是主流写法,在模板、函数中使用变量更简单,不用写 student.
注意
可能有的人看到这里会迷糊,既然需要 解构,那为什么还要定义 student 对象?为什么还要将 name、age 等属性定义在对象中?
因为下面属性是描述一个学生的:
name: '李雷',
age: 20,
sex: '男',
height: 1.82
按照面向对象的编程思想,就应该放到对象中
解构是为了在模板、函数中使用变量更简单,不用写 student.
解决
使用 toRefs、toRef 解构后仍保留响应式
toRefs() 函数
toRefs 接收一个 响应式对象(reactive 创建),把对象每一层属性都转为独立的 ref,返回一个普通对象,所有属性都是 ref。
规则
- 参数只能是
reactive/shallowReactive响应式对象; - 返回新对象,原对象不变;
- 每个属性都是
ref,取值需要.value; - 只转换自身可枚举属性,不处理深层对象;
- 不能用于原始值、ref、数组。
例子
<template>
姓名:{{name}}<br>
年龄:{{age}}<br>
性别:{{sex}}<br>
身高:{{height}}<br>
<button @click="addAge">年龄加1</button>
<button @click="modifyName">修改名字</button>
</template>
<script lang="ts" setup name='Student'>
import { reactive,toRefs } from 'vue'
let student = reactive({
name: '李雷',
age: 20,
sex: '男',
height: 1.82
})
let { name, age, sex, height } = toRefs(student)
// 还是ref对象
console.log("name:",name)
console.log("age:",age)
console.log("sex:",sex)
function addAge() {
// 通过 .value 改变值
age.value++
}
function modifyName() {
// 通过 .value 改变值
name.value = '李大雷'
}
</script>
<style>
</style>
解释
name、age等变量 是Ref实例,模板自动解包,修改时同步更新原student。模板里不用
.value,script 内部必须加.value
toRef() 函数
提取对象某一个属性,生成关联原对象的 ref
语法
toRef(target, key, defaultValue?)
参数解释:
target:reactive 响应式对象key:要提取的属性名,字符串类型defaultValue?:当 target 上不存在 key 这个属性时,ref 默认取该值。不会自动同步到原对象。可略
例子
<template>
姓名:{{name}}<br>
年龄:{{age}}<br>
性别:{{sex}}<br>
身高:{{height}}<br>
<button @click="addAge">年龄加1</button>
<button @click="modifyName">修改名字</button>
</template>
<script lang="ts" setup name='Student'>
import { reactive,toRef } from 'vue'
let student = reactive({
name: '李雷',
age: 20,
sex: '男',
height: 1.82
})
let name = toRef(student,'name')
let age = toRef(student,'age')
let sex= toRef(student,'sex')
let height = toRef(student,'height')
// 还是ref对象
console.log("name:",name)
console.log("age:",age)
console.log("sex:",sex)
function addAge() {
// 通过 .value 改变值
age.value++
}
function modifyName() {
// 通过 .value 改变值
name.value = '李大雷'
}
</script>
<style>
</style>
toRefs vs toRef 区别
toRefs(obj):批量把对象所有属性转 ref,返回对象,适合解构;toRef(obj, key):单独转换对象某一个属性,只生成单个 ref。
注意:深层对象处理
toRefs 只处理第一层,嵌套对象内部不会自动转 ref:
const state = reactive({
user: {
username: 'test'
}
})
const { user } = toRefs(state)
// user 是 ref,user.value 仍是 reactive 对象
console.log(user.value.username)
// 修改嵌套
user.value.username = 'admin'
如果需要深层全部转 ref,需递归处理或使用 toRefs 逐层解构。
常见坑 & 注意事项
坑1:传入普通对象,无响应
const obj = { name: 'xxx' }
const { name } = toRefs(obj) // 无效,不会响应
必须传入 reactive 代理对象。
坑2:数组不能用 toRefs
toRefs 设计用于对象,数组请用 toRef 单独取值或 ref([])。
const arr = reactive([1,2,3])
const refs = toRefs(arr) // 不推荐,行为异常
坑3:新增属性不会被 toRefs 捕获
toRefs 只会转换调用时已存在的属性,后续新增属性无法解构:
const state = reactive({ name: '张三' })
const refs = toRefs(state)
state.age = 18 // 后新增,refs 里没有 age
坑4:不要覆盖 ref 变量
const { name } = toRefs(state)
name = '新值' // 错误!直接覆盖ref对象,丢失响应
name.value = '新值' // 正确
总结
toRefs(reactiveObj)将响应式对象所有属性批量转为 ref;- 解决解构
reactive丢失响应式问题; - 模板直接使用,script 操作加
.value; - 组合式函数抽离逻辑时标准返回方案;
- 仅处理第一层对象,不递归深层;只接受 reactive 对象。