shallowRef用于全局状态中隔离重型只读数据,仅响应.value整体替换,避免深度代理开销;适用于万级静态字典、第三方实例、undo/redo快照等场景,兼顾响应性与性能。

shallowRef 本身不直接用于构建全局状态库(比如替代 Pinia 或 Vuex),但它能显著降低全局状态中“重型只读数据”的响应式开销——关键在于分层治理:顶层状态用 ref/reactive 管理逻辑,而内部的大块静态/第三方数据用 shallowRef 隔离代理。
适合用 shallowRef 的全局状态部分
不是整个 store,而是 store 中这几类字段:
- 万级只读数据源:如全局缓存的城市列表、权限码表、API 返回的原始字典数组。它们加载后几乎不变,但若用 ref 包裹,Vue 会为每条记录创建 Proxy,内存暴涨且无意义
-
第三方实例引用:比如 store 中保存的
mapInstance(MapLibre)、editor(Monaco)、chart(ECharts)。这些对象自带完整状态和生命周期,深度响应式会破坏其私有属性访问,甚至引发Cannot read private member报错 -
离线快照或历史版本:如 undo/redo 栈里保存的旧 state 快照。你只在切换时整体替换
history.value[i],从不修改其内部字段
在 Pinia store 中的实际写法
以一个含地图实例和城市列表的 store 为例:
递归分析 Vue 项目组件依赖,从入口文件生成组件层级图,支持 Vue 2/3,输出组件名、文件路径和属性。适用于分析组件结构、排查依赖或了解项目架构。
import { defineStore } from 'pinia'
import { shallowRef, triggerRef } from 'vue'
export const useAppStore = defineStore('app', () => {
// ✅ 用 shallowRef 托管重型只读数据
const cities = shallowRef([]) // 城市列表,只在初始化/刷新时整体赋值
const mapInstance = shallowRef(null) // 地图实例,保持原生,不被 Proxy 劫持
// ✅ 普通业务状态仍用 ref/reactive
const user = ref({ name: '', role: '' })
const filters = reactive({ region: '', type: '' })
// ✅ 加载城市列表(整体替换,触发响应)
async function loadCities() {
const list = await api.getCities()
cities.value = list // 触发视图更新
}
// ✅ 初始化地图(整体赋值,不递归代理)
function initMap(dom) {
const instance = new MapLibreGL({ container: dom })
mapInstance.value = instance // 安全,instance 保持原始形态
}
// ✅ 局部修改 + 手动触发(如更新某城市名称后需重绘)
function updateCityName(id, newName) {
const city = cities.value.find(c => c.id === id)
if (city) city.name = newName
triggerRef(cities) // 显式通知依赖更新
}
return {
cities,
mapInstance,
user,
filters,
loadCities,
initMap,
updateCityName
}
})
为什么比 markRaw 更合适?
markRaw 让对象完全脱离响应式体系,连 .value = newObj 都不会触发更新;而 shallowRef 保留了顶层赋值的响应能力——这正是全局状态需要的:数据可换,但不能失联。它既避免了深度代理的开销,又维持了与组件模板的响应连接。
立即学习“前端免费学习笔记(深入)”;
配合策略效果更佳
- 对 cities 这类纯展示数据,在模板中搭配
v-once或虚拟滚动,进一步减少 diff 开销 - 在 store 的 getter 中用
computed(() => cities.value)封装,便于测试和复用 - 配合
watch监听cities变化做副作用(如埋点、日志),而非监听内部字段

















