一、什么是shallowRef和shallowReactive
在Vue3的Composition API中,除了常用的ref和reactive函数外,Vue还提供了两个特殊的响应式API:shallowRef和shallowReactive。它们的特点是非深度响应,用于特定的性能优化场景。
1.1 核心概念
- shallowRef:只对基本类型数据进行响应式处理,对于对象类型,只监听
.value的变化,不监听对象内部属性的变化。 - shallowReactive:只对对象的第一层属性进行响应式处理,不递归处理嵌套对象。
二、为什么需要shallow API
2.1 深度响应的性能问题
- ref和reactive的深度监听:默认情况下,
ref和reactive会递归地将所有嵌套对象都转换为响应式,这在处理大型复杂数据结构时会带来性能开销。 - 不必要的响应式:在某些场景下,我们并不需要对所有层级的数据都进行响应式处理,尤其是那些不会改变或不需要UI同步的深层数据。
- 性能优化:对于大型数据结构,使用浅响应式可以显著减少内存使用和提高渲染性能。
三、shallowReactive的使用
3.1 基本用法
<template>
<div>
<h1>姓名:{{ user.name }}</h1>
<h1>年龄:{{ user.profile.age }}</h1>
<button @click="updateName">修改姓名</button>
<button @click="updateAge">修改年龄</button>
</div>
</template>
<script setup>
import { shallowReactive, toRefs } from "vue";
// 创建浅响应式对象
const user = shallowReactive({
name: "张三",
profile: {
age: 25,
city: "北京",
},
});
// 解构为ref以便在模板中使用
const { name, profile } = toRefs(user);
// 修改第一层属性 - 会触发响应式更新
const updateName = () => {
user.name = "李四";
};
// 修改深层属性 - 不会触发响应式更新
const updateAge = () => {
user.profile.age++;
console.log("年龄已修改为:", user.profile.age); // 控制台会显示更新,但UI不会更新
};
</script>
3.2 特征和限制
- 只响应第一层:
shallowReactive只会对对象的第一层属性进行响应式处理。 - 深层属性修改:修改深层属性不会触发UI更新,但数据本身会被修改。
- 强制更新:
shallowReactive不支持triggerRef,如需在修改深层属性后触发更新,需要替换整个对象或改用shallowRef配合triggerRef。
3.3 适用场景
- 大型静态数据:当你有一个很大的对象,但只需要其中一小部分属性是响应式的。
- 第三方库数据:当你使用第三方库返回的数据,且不需要对其进行深度响应式处理。
- 性能敏感场景:在需要频繁更新数据的场景下,减少响应式开销。
四、shallowRef的使用
4.1 基本用法
<template>
<div>
<h1>基本类型:{{ count }}</h1>
<h1>对象类型 - 姓名:{{ user.name }}</h1>
<h1>对象类型 - 年龄:{{ user.age }}</h1>
<button @click="updateCount">修改基本类型</button>
<button @click="updateUserProp">修改对象属性</button>
<button @click="replaceUser">替换整个对象</button>
</div>
</template>
<script setup>
import { shallowRef } from "vue";
// 基本类型 - 会正常响应
const count = shallowRef(0);
// 对象类型 - 只监听.value的变化
const user = shallowRef({
name: "张三",
age: 25,
});
// 修改基本类型 - 会触发响应式更新
const updateCount = () => {
count.value++;
};
// 修改对象属性 - 不会触发响应式更新
const updateUserProp = () => {
user.value.age++;
console.log("年龄已修改为:", user.value.age); // 控制台会显示更新,但UI不会更新
};
// 替换整个对象 - 会触发响应式更新
const replaceUser = () => {
user.value = {
name: "李四",
age: 30,
};
};
</script>
4.2 特征和限制
- 只监听.value:
shallowRef只监听.value的变化,对于对象内部属性的变化不敏感。 - 基本类型正常:对于基本类型数据,
shallowRef的行为与ref相同。 - 对象类型特殊:对于对象类型,只有替换整个对象时才会触发更新。
4.3 适用场景
- 频繁更新的基本类型:如计数器、开关状态等。
- 大型对象:当你有一个大型对象,但只需要在替换整个对象时触发更新。
- 与triggerRef配合:在需要时手动触发更新。
五、shallowRef与ref的对比
| 特性 | ref | shallowRef |
|---|---|---|
| 基本类型处理 | 响应式 | 响应式 |
| 对象类型处理 | 深度响应式 | 只监听.value变化 |
| 性能开销 | 较高(深度监听) | 较低(浅监听) |
| 适用场景 | 大多数情况 | 大型对象或只需要替换整个对象的场景 |
六、shallowReactive与reactive的对比
| 特性 | reactive | shallowReactive |
|---|---|---|
| 响应深度 | 深度响应式 | 只响应第一层 |
| 性能开销 | 较高(递归转换) | 较低(只处理第一层) |
| 适用场景 | 大多数情况 | 大型对象或只需要第一层响应的场景 |
七、shallowRef和shallowReactive的高级使用技巧
7.1 手动触发更新
<template>
<div>
<h1>用户信息:{{ user.name }} - {{ user.age }}</h1>
<button @click="updateDeepProp">修改深层属性</button>
</div>
</template>
<script setup>
import { shallowRef, triggerRef } from "vue";
const user = shallowRef({
name: "张三",
age: 25,
address: {
city: "北京",
district: "朝阳区",
},
});
const updateDeepProp = () => {
// 修改深层属性
user.value.address.city = "上海";
// 手动触发更新
triggerRef(user);
console.log("城市已修改为:", user.value.address.city);
};
</script>
7.2 与readonly配合使用
<script setup>
import { shallowReactive, readonly } from "vue";
// 创建只读的浅响应式对象
const config = readonly(
shallowReactive({
apiUrl: "https://api.example.com",
timeout: 3000,
headers: {
"Content-Type": "application/json",
Authorization: "Bearer token",
},
})
);
// 尝试修改会失败
// config.apiUrl = 'https://new-api.example.com' // 会警告
// 使用shallowRef配合readonly
import { shallowRef } from "vue";
const configRef = shallowRef(
readonly({
apiUrl: "https://api.example.com",
timeout: 3000,
})
);
// 可以替换整个对象
configRef.value = readonly({
apiUrl: "https://new-api.example.com",
timeout: 5000,
headers: {
"Content-Type": "application/json",
Authorization: "Bearer new-token",
},
});
</script>
7.3 性能优化场景
7.3.1 场景1:大型表格数据
<script setup>
import { shallowRef } from "vue";
// 大型表格数据,只需要在数据完全替换时更新UI
const tableData = shallowRef([]);
// 加载数据
const loadData = async () => {
const data = await fetchLargeDataSet();
// 替换整个数组,触发更新
tableData.value = data;
};
// 局部更新(不会触发UI更新,因为shallowRef只监听.value变化)
tableData.value[0].status = "completed";
// 如果需要UI更新,使用triggerRef手动触发
// triggerRef(tableData)
</script>
7.3.2 场景2:复杂配置对象
<script setup>
import { shallowReactive } from "vue";
// 复杂配置对象,只有顶层属性需要响应式
const config = shallowReactive({
theme: "light",
language: "zh-CN",
// 深层配置不需要响应式
advanced: {
performance: {
cacheSize: 1024,
timeout: 3000,
},
security: {
csrf: true,
xss: true,
},
},
});
// 修改顶层属性 - 触发更新
config.theme = "dark";
// 修改深层属性 - 不触发更新
config.advanced.performance.cacheSize = 2048;
</script>
八、常见陷阱和解决方案
8.1 忘记shallowRef的非深度特性
问题:修改shallowRef包装的对象内部属性时,UI不更新。
解决方案:
- 使用
triggerRef手动触发更新 - 或使用普通
ref(如果需要深度响应) - 或替换整个对象
8.2 混淆shallowReactive和reactive
问题:在需要深度响应的场景下使用了shallowReactive,导致深层属性修改不触发更新。
解决方案:
- 明确区分使用场景
- 对于需要深度响应的数据,使用
reactive - 对于只需要第一层响应的数据,使用
shallowReactive
8.3 性能优化过度
问题:在不需要性能优化的场景下使用了shallow API,增加了代码复杂度。
解决方案:
- 优先使用普通的
ref和reactive - 只在确实需要性能优化的场景下使用shallow API
- 进行性能测试,确认优化效果
九、最佳实践
- 默认使用ref和reactive:在大多数情况下,使用默认的深度响应式API。
- 按需使用shallow API:只在以下场景使用:
- 处理大型数据结构
- 数据结构深度较深
- 只有部分属性需要响应式
- 性能敏感的场景
- 明确注释:在使用shallow API时,添加注释说明原因和使用注意事项。
- 结合triggerRef:在需要时使用
triggerRef手动触发更新。 - 测试验证:在使用shallow API后,测试确保功能正常且性能得到提升。
十、代码优化建议
10.1 合理选择响应式API
// 优化前:对于大型对象使用reactive
const largeData = reactive({
// 大型嵌套对象
});
// 优化后:对于大型对象使用shallowReactive
const largeData = shallowReactive({
// 大型嵌套对象
});
10.2 结合computed使用
import { shallowRef, computed } from "vue";
const user = shallowRef({
name: "张三",
age: 25,
});
// 使用computed处理派生状态
const isAdult = computed(() => user.value.age >= 18);
10.3 批量更新策略
import { shallowRef, triggerRef } from "vue";
const data = shallowRef({
// 大量数据
});
// 批量修改
function batchUpdate() {
// 多次修改深层属性
data.value.items.forEach(item => {
item.status = "processed";
});
// 最后手动触发一次更新
triggerRef(data);
}
十一、总结
shallowRef和shallowReactive是Vue3提供的性能优化工具,它们通过减少响应式系统的开销来提高应用性能。正确使用这些API可以显著改善大型应用的性能,但需要注意它们的局限性:
- shallowRef:只监听
.value的变化,适用于基本类型和需要整体替换的对象。 - shallowReactive:只响应第一层属性,适用于大型对象且只需要第一层响应的场景。
在实际开发中,应该根据具体场景选择合适的响应式API,在性能优化和代码可维护性之间找到平衡。