浅谈Vue中shallowRef和shallowReactive什么时候用

 更新时间:2026年08月17日 09:34:11   作者:JJCTO袁龙  
本文用5个高频场景告诉你shallowRef和shallowReactive怎么选,配合triggerRef和markRaw,轻松跳过深层响应式追踪,显著提升渲染性能

一句话总结:当你有一个大对象且只关心顶层引用变化时,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 / markRawECharts、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整体架构浅析

    这篇文章主要介绍了vue-cli系列之vue-cli-service整体架构浅析,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-01-01
  • vue项目中使用fetch的实现方法

    vue项目中使用fetch的实现方法

    这篇文章主要介绍了vue项目中使用fetch的实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-04-04
  • 解决vue中this.$set()不更新问题

    解决vue中this.$set()不更新问题

    我在做一个附件删除功能的时候发现,明明打印出来附件已经没有数据了但是页面就是不刷新, this.$set()不生效,所以本文小编给大家介绍了vue中this.$set()不更新的解决方法,需要的朋友可以参考下
    2023-11-11
  • vue+elementUI用户修改密码的前端验证规则

    vue+elementUI用户修改密码的前端验证规则

    用户登录后修改密码,密码需要一定的验证规则,这篇文章主要介绍了vue+elementUI用户修改密码的前端验证,需要的朋友可以参考下
    2024-03-03
  • el-table实现转置表格的示例代码(行列互换)

    el-table实现转置表格的示例代码(行列互换)

    这篇文章主要介绍了el-table实现转置表格的示例代码(行列互换),本文结合示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2024-02-02
  • Vue js如何用split切分并去掉空值和item的空格

    Vue js如何用split切分并去掉空值和item的空格

    这篇文章主要介绍了Vue js如何用split切分并去掉空值和item的空格,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-06-06
  • vue路由传参及获取参数的方式总结

    vue路由传参及获取参数的方式总结

    这篇文章主要介绍了vue路由传参及获取参数的方式总结,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-05-05
  • Vue 3 中使用 OpenLayers 实时显示 zoom 及四角坐标的实现代码

    Vue 3 中使用 OpenLayers 实时显示 zoom 

    本文介绍了如何在 Vue 3 中使用 OpenLayers 来获取地图的 zoom 值以及四角坐标信息,并实时更新数据,这种方式可以用于 GIS 应用开发,帮助用户更好地了解当前地图范围,感兴趣的朋友一起看看吧
    2025-04-04
  • vue中前端代理跨域问题实例总结

    vue中前端代理跨域问题实例总结

    前后端分离进行项目开发,跨域问题不可避免,下面这篇文章主要给大家介绍了关于vue中前端代理跨域问题的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2022-04-04
  • vue.js实现插入数值与表达式的方法分析

    vue.js实现插入数值与表达式的方法分析

    这篇文章主要介绍了vue.js实现插入数值与表达式的方法,结合实例形式分析了vue.js常见的3种插入数值实现方式,并总结了vue.js插值与表达式相关使用技巧,需要的朋友可以参考下
    2018-07-07

最新评论