# Vue 3 性能优化利器:一文搞懂 shallowRef 的核心原理与最佳实践
在 Vue 3 开发中,大多数人习惯了一律使用 `ref` 或 `reactive`。然而,在处理某些特定场景(如第三方实例、巨型数据集)时,深层响应式转换会导致**严重的内存开销与页面卡顿**。
本文将深入拆解 `shallowRef` 的作用、原理以及 3 大黄金应用场景。
---
## 一、什么是 `shallowRef`?
`shallowRef` 是 `ref` 的**浅层响应式版本**。
- **`ref`(深层响应式)**:会深层递归遍历传入对象的所有属性,通过 `Proxy` 将它们全部转为响应式。
- **`shallowRef`(浅层响应式)**:**只追踪 `.value` 引用本身的变化**,不会深层代理对象内部的属性。
### 💻 行为对比
```typescript
import { ref, shallowRef } from 'vue'
// 1. 普通 ref
const state1 = ref({ count: 0 })
state1.value.count++ // ✅ 页面会更新 (深层追踪)
state1.value = { count: 1 } // ✅ 页面会更新
// 2. shallowRef
const state2 = shallowRef({ count: 0 })
state2.value.count++ // ❌ 页面不会更新!(不监听深层属性)
state2.value = { count: 1 } // ✅ 页面会更新!(修改了整个 .value 引用)
二、3 大黄金应用场景
使用 shallowRef 的核心目标只有一个:极致的性能优化。
1. 存储第三方重型 JS 实例(ECharts / 地图 / 视频播放器 / 渲染引擎)
第三方类库实例(如 ECharts 实例、WebGl 渲染器、地图 SDK 等)内部包含数千个私有属性、方法、循环引用与原生 DOM。
- 隐患:如果使用普通
ref(instance),Vue 会强制把这几千个属性全部Proxy代理一遍,极易导致内存暴涨、页面卡顿,甚至破坏实例内部的this指向。 - 解法:使用
shallowRef。
import { shallowRef, onMounted } from 'vue'
import * as echarts from 'echarts'
// ✅ 存储 ECharts 实例
const chartInstance = shallowRef<echarts.ECharts | null>(null)
onMounted(() => {
const dom = document.getElementById('my-chart')!
chartInstance.value = echarts.init(dom)
})
2. 纯展示/全量替换的大数据列表(大数据表格、日志列表)
当从后端拉取了数万条数据的列表,且前端业务逻辑只涉及整体替换(如翻页、重新搜索),而不会单独修改某一条数据的内部字段(如 list[5].name = 'xx'):
import { shallowRef } from 'vue'
// ✅ 避免对万级数据做深层 Proxy 递归,大幅提升页面加载与渲染速度
const bigTableData = shallowRef<UserItem[]>([])
async function loadData() {
// 重新赋值整个 .value 即可触发视图更新
bigTableData.value = await fetchHugeList()
}
3. 保存动态组件类(Vue 官方推荐)
在切换选项卡(Tab)动态加载组件时,组件对象本身不需要被响应式代理:
import { shallowRef } from 'vue'
import TabA from './TabA.vue'
import TabB from './TabB.vue'
// ✅ 官方推荐:保存当前活跃的动态组件
const activeComponent = shallowRef(TabA)
function switchTab(type: 'A' | 'B') {
activeComponent.value = type === 'A' ? TabA : TabB
}
三、补充技巧:triggerRef 强制刷新
如果你使用 shallowRef 修改了内部字段,但在特定条件下临时需要更新一次视图,可以使用 triggerRef 强制触发响应:
import { shallowRef, triggerRef } from 'vue'
const userInfo = shallowRef({ name: 'John', age: 18 })
userInfo.value.age = 19 // 视图默认不更新
triggerRef(userInfo) // 📢 强制通知 Vue 刷新 DOM!
💡 总结记忆口诀
“存实例、存大数,只需全量换,统统用
shallowRef”。