浅谈Vue中shallowRef和shallowReactive什么时候用
一句话总结:当你有一个大对象且只关心顶层引用变化时,shallowRef / shallowReactive 能帮你跳过深层响应式追踪,换来显著的性能提升。
shallowRef 和 shallowReactive 是什么
Vue 3 默认对 ref 和 reactive 做深层响应式处理——对象的每一层属性都会被 Proxy 包裹。这在大多数场景下没问题,但如果对象特别大(比如表格数据几千行、富文本 JSON 树),深层代理的开销会肉眼可见。
| API | 深层响应式 | 适用场景 |
|---|---|---|
| ref(obj) | ✅ 深层 | 需要修改嵌套属性并触发更新 |
| reactive(obj) | ✅ 深层 | 需要修改嵌套属性并触发更新 |
| shallowRef(obj) | ❌ 仅 .value 变化才更新 | 大对象、只整体替换 |
| shallowReactive(obj) | ❌ 仅顶层属性变化才更新 | 只改顶层属性 |
import { ref, reactive, shallowRef, shallowReactive, triggerRef } from 'vue';
// ref:修改嵌套属性会触发更新
const state1 = ref({ count: 0 });
state1.value.count = 1; // ✅ 触发更新
// shallowRef:修改嵌套属性不会触发更新
const state2 = shallowRef({ count: 0 });
state2.value.count = 1; // ❌ 不会触发更新
state2.value = { count: 1 }; // ✅ 触发更新(替换整个 .value)
// shallowReactive:顶层属性变化触发更新,嵌套不触发
const state3 = shallowReactive({ user: { name: '张三' }, count: 0 });
state3.count = 1; // ✅ 触发更新
state3.user.name = '李四'; // ❌ 不会触发更新
5 个高频使用场景
场景一:大表格数据渲染
后端返回几千条表格数据,你只需要整体赋值,不需要修改单行的嵌套属性。
❌ 错误写法:用 ref 包裹大数组
import { ref } from 'vue';
const tableData = ref([]);
const loadData = async () => {
const res = await fetchTableData(); // 返回 5000 条数据
tableData.value = res.data; // Vue 会对每一行做深层代理,页面卡顿 1-2 秒
};
✅ 正确写法:用 shallowRef
import { shallowRef } from 'vue';
const tableData = shallowRef([]);
const loadData = async () => {
const res = await fetchTableData(); // 5000 条数据
tableData.value = res.data; // 只代理 .value 这一层,瞬间完成
};
场景二:富文本编辑器 / JSON 编辑器
编辑器内部维护的文档结构可能是几百层的嵌套对象,你不需要 Vue 追踪每一层变化。
❌ 错误写法:用 reactive 包裹编辑器状态
import { reactive } from 'vue';
const editorState = reactive({
doc: { /* 深度嵌套的文档结构 */ },
selection: { /* 嵌套选择信息 */ }
});
// Vue 试图代理整个文档树,初始化就卡了
✅ 正确写法:用 shallowReactive
import { shallowReactive } from 'vue';
const editorState = shallowReactive({
doc: null,
selection: null,
mode: 'edit'
});
// 只追踪 mode、doc、selection 这些顶层属性
// doc 内部变化由编辑器库自己管理
const changeMode = (newMode) => {
editorState.mode = newMode; // ✅ 触发更新
};
场景三:ECharts / 地图实例存储
第三方图表库的实例对象非常大且内部结构复杂,Vue 不应该代理它。
❌ 错误写法:用 ref 存储 ECharts 实例
import { ref, onMounted } from 'vue';
import * as echarts from 'echarts';
const chartInstance = ref(null);
onMounted(() => {
chartInstance.value = echarts.init(document.getElementById('chart'));
// Vue 会深度代理 echarts 实例,可能导致性能问题或库内部行为异常
});
✅ 正确写法:用 shallowRef
import { shallowRef, onMounted } from 'vue';
import * as echarts from 'echarts';
const chartInstance = shallowRef(null);
onMounted(() => {
chartInstance.value = echarts.init(document.getElementById('chart'));
// Vue 不会代理 echarts 实例内部,安全且高效
});
const updateChart = () => {
chartInstance.value?.setOption({ /* ... */ });
};
场景四:修改了 shallowRef 内部数据但需要触发更新
使用 shallowRef 后,修改内部属性不会触发更新。如果你确实需要手动通知 Vue 更新,用 triggerRef。
❌ 错误写法:直接修改后期待自动更新
import { shallowRef } from 'vue';
const list = shallowRef([{ name: 'A' }, { name: 'B' }]);
// 修改嵌套属性——界面不会更新
list.value[0].name = 'C';
// 替换数组才行,但太浪费
list.value = [...list.value];
✅ 正确写法:用 triggerRef 手动触发
import { shallowRef, triggerRef } from 'vue';
const list = shallowRef([{ name: 'A' }, { name: 'B' }]);
// 修改嵌套属性后手动触发更新
list.value[0].name = 'C';
triggerRef(list); // ✅ 通知 Vue 重新渲染
场景五:配合 markRaw 处理不需要响应式的第三方对象
有些对象永远不需要响应式,用 markRaw 永久标记,比 shallowRef 更彻底。
❌ 错误写法:把不需要响应式的数据也放进 ref
import { ref } from 'vue';
const state = ref({
config: { /* 大量静态配置 */ },
data: []
});
// config 永远不会变,但 Vue 还是代理了它
✅ 正确写法:markRaw 排除不需要代理的部分
import { ref, markRaw } from 'vue';
const staticConfig = markRaw({
chartOptions: { /* 大量静态配置 */ },
constants: { /* 常量 */ }
});
const state = ref({
config: staticConfig, // 不会被代理
data: [] // 正常响应式
});
万能兜底方案
选择决策流程:
数据需要响应式吗?
├─ 否 → markRaw 永久排除
└─ 是 → 数据量大吗?
├─ 小(< 100 个属性)→ ref / reactive
└─ 大(表格 / 图表 / 编辑器)
├─ 只整体替换 → shallowRef
├─ 只改顶层属性 → shallowReactive
└─ 偶尔改嵌套 → shallowRef + triggerRef通用封装模板:
import { shallowRef, triggerRef, type Ref } from 'vue';
/**
* 创建一个浅层响应式列表
* 适合大数据表格:整体替换触发更新,局部修改手动触发
*/
export function useShallowList(initial = []) {
const list = shallowRef([...initial]);
const replace = (newList) => {
list.value = newList; // 自动触发更新
};
const updateItem = (index, patch) => {
if (list.value[index]) {
Object.assign(list.value[index], patch);
triggerRef(list); // 手动触发
}
};
const remove = (index) => {
list.value.splice(index, 1);
triggerRef(list);
};
return { list, replace, updateItem, remove };
};
// 使用
const { list, replace, updateItem } = useShallowList();
预防 Checklist
| 检查项 | 说明 |
|---|---|
| 数据量是否足够大 | 少于 100 个属性用 ref 就行,别过度优化 |
| 是否只需要整体替换 | 是 → shallowRef;否 → 考虑 shallowReactive |
| 修改嵌套属性后有没有 triggerRef | 忘了调就界面不更新 |
| 第三方实例有没有用 shallowRef / markRaw | ECharts、mapbox、monaco 等实例必须排除 |
| 团队成员是否知道这是浅层响应式 | 加注释说明,避免别人直接改嵌套属性 |
| watch 能不能监听到 | shallowRef 只监听 .value 变化,shallowReactive 只监听顶层 |
| toRefs 解构后是否还是响应式 | shallowReactive 用 toRefs 解构后仍是浅层 |
一句话总结
shallowRef 和 shallowReactive 是 Vue 性能优化的利器——大数据只管整体替换就选 shallowRef,只改顶层属性就选 shallowReactive,偶尔改嵌套就配 triggerRef 手动通知,第三方实例直接上 markRaw。
到此这篇关于浅谈Vue中shallowRef和shallowReactive什么时候用的文章就介绍到这了,更多相关Vue shallowRef shallowReactive内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
vue-cli系列之vue-cli-service整体架构浅析
这篇文章主要介绍了vue-cli系列之vue-cli-service整体架构浅析,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧2019-01-01
Vue 3 中使用 OpenLayers 实时显示 zoom 
本文介绍了如何在 Vue 3 中使用 OpenLayers 来获取地图的 zoom 值以及四角坐标信息,并实时更新数据,这种方式可以用于 GIS 应用开发,帮助用户更好地了解当前地图范围,感兴趣的朋友一起看看吧2025-04-04


最新评论