vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > Vue shallowRef shallowReactive

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

作者: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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文