Vue3浅层响应式API全解析:shallowRef、shallowReactive与shallowReadonly深度探索
Vue 3 浅层响应式 API 全解析:shallowRef、shallowReactive 与 shallowReadonly 深度探索
目录
- 前言
- 为什么需要“浅层”响应式
- 3.1 shallowRef
- 3.2 shallowReactive
- 3.3 shallowReadonly
- 4.1 深度 vs 浅层:响应式行为对比
- 4.2 性能与使用场景
- 5.1 基本概念与语法
- 5.2 代码示例
- 5.3 内部原理剖析(图解)
- 5.4 使用场景与注意事项
- 6.1 基本概念与语法
- 6.2 代码示例
- 6.3 内部原理剖析(图解)
- 6.4 使用场景与注意事项
- 7.1 基本概念与语法
- 7.2 代码示例
- 7.3 内部原理剖析(图解)
- 7.4 使用场景与注意事项
- 综合示例:三种浅层 API 联合使用
- 常见误区与解答
- 总结
前言
Vue 3 推出了全新的响应式系统(基于 Proxy 实现),不仅在性能上大幅提升,还提供了更多灵活的 API 供开发者使用。其中,浅层响应式(shallow)API 是一个“轻量级”选项,让我们在只需要对顶层属性进行响应式跟踪时,大幅减少不必要的代理开销。本文将从概念、原理、代码示例和实战角度,深度解析 shallowRef
、shallowReactive
与 shallowReadonly
三个 API,让你对“浅层响应式”有更直观清晰的认识。
本文适合已经了解 Vue 3 普通 ref/ reactive/ readonly
基础用法的开发者。如果你对 Vue 3 响应式系统有初步了解,但不清楚“何时需要浅层响应式”、“浅层响应式如何实现”、以及“浅层与深度响应式的具体差异”,请耐心阅读,相信本文能够帮你快速上手并灵活运用这三种 API。
为什么需要“浅层”响应式
在实际项目中,我们常常面临下面几种场景:
- 大而深的对象
某个状态可能是一个深度嵌套的对象(例如:复杂配置、第三方数据),但我们只关注顶层某个引用是否变化,而不需要追踪内部每一个属性的实时更新。 - 外部数据接管
当你从接口获取了一个大型对象,并不想对其内部逐层代理,只需要知道何时整块数据引用改变。 - 性能瓶颈
深度递归地为每个属性和子属性都创建 Proxy,虽然 Vue 3 的 Proxy 性能已经很出色,但对于特别巨量的对象,还是会有额外的内存与运行时消耗。
如果使用普通的 ref
或 reactive
,Vue 会对整个对象进行深度代理,对每层属性进行依赖收集与触发,开销相对较大。而“浅层”响应式则只对最外层进行代理,内部层级在第一次访问时并不会再被转换成响应式。这样,在“只关心外层引用”或“只需浅层响应式”的场景里,就能大幅节省框架开销。
浅层 API 一览
Vue 3 共提供了三种浅层版本的响应式 API:
shallowRef(value)
与普通ref
相比,只会对value
本身建立响应式,而不会递归地将value
内部的对象/数组转换为响应式。shallowReactive(object)
与普通reactive
相比,只会对object
顶层属性建立 Proxy,而不对嵌套对象进行深度转换。shallowReadonly(object)
与普通readonly
相比,只会对object
顶层属性建立只读(无法修改),但内部嵌套对象仍是“可写”的普通对象。
下面,我们会分别对这三个 API 进行深入剖析,并通过示例与图解来帮助你理解它们之间的异同。
与常规 ref/reactive/readonly 的对比
在讲各个浅层 API 之前,我们先从整体上对比一下“深度”与“浅层”响应式的区别。
深度 vs 浅层:响应式行为对比
API | 响应式深度 | 顶层访问响应式 | 嵌套属性访问响应式 | 修改内部属性是否触发外层依赖更新 |
---|---|---|---|---|
ref(value) | 仅值层(如果 value 是基本类型)如果 value 是对象,则深度代理(递归) | ✔ | ✔ | ✔ |
reactive(object) | 深度递归代理 | ✔ | ✔ | ✔ |
shallowRef(value) | 仅顶层(不递归) | ✔ | ❌(内部不代理) | ❌(内部改动不会触发) |
shallowReactive(obj) | 顶层属性(不递归) | ✔ | ❌ | ❌ |
readonly(object) | 深度递归只读 | ✔ | ✔ | —— (无法修改,抛错或警告) |
shallowReadonly(obj) | 顶层属性只读 | ✔ | ❌(内部可写) | 可写但不会触发只读警告 |
- 深度递归 的
reactive
和readonly
会在访问任何一层嵌套属性时,都自动将其转换为 Proxy,并对其进行依赖收集或只读保护。 - 浅层 版本则只对最外层进行响应式或只读,内部嵌套对象保持原始状态。当你访问二级或多级属性时,Vue 不会再转换成 Proxy,也不会收集依赖。
性能与使用场景
场景 | 使用“深度”API | 使用“浅层”API |
---|---|---|
需要对子属性进行精细化追踪与渲染 | √ | × |
只需要顶层引用或属性更改即可触发视图更新 | √(可用) | √(推荐,减少代理开销) |
数据对象非常庞大且深度嵌套,不关注内部字段变化 | ×(性能低) | √(浅层代理,更轻量) |
需要浅层只读保护,内部子属性仍可写 | ×(会报错或警告) | √(只对顶层只读,内部可写) |
外部传入的外部库对象,仅需监听何时整体替换 | ×(会代理内部) | √(只代理最外层引用) |
关键要点:
- 若你只关心 外层引用/属性 的变化,或不想对子对象嵌套层级都进行代理,那么使用 浅层 API 更合理,也能带来性能优势。
- 若你需要对内部字段进行深度追踪与响应,则应使用深度 API(普通
reactive
/readonly
/ref
)。
shallowRef 详解
5.1 基本概念与语法
import { shallowRef } from 'vue';
shallowRef(value)
会返回一个对象{ value: value }
,与普通ref
类似。- 差异在于: 如果
value
是一个对象/数组,调用shallowRef
时 Vue 只会在最外层对其做一个响应式容器(拦截对ref.value
本身的赋值/读取),而不会递归地将value
内部的属性转为响应式。
典型用法:
const obj = { a: 1, b: { c: 2 } };
const r = shallowRef(obj);
// 1. 访问 r.value 时,返回的就是原始对象 obj(未被深度代理)
// 2. 当你修改 r.value = newObj 时,会触发依赖更新
// 3. 如果你直接修改 r.value.b.c = 3,Vue 不会检测到,也不会触发依赖更新
5.2 代码示例
<template>
<div>
<h3>shallowRef 示例</h3>
<p>浅层对象:{{ rObj }}</p>
<button @click="updateNested">修改内部属性 b.c = 3</button>
<button @click="replaceObj">替换整个对象</button>
<p>渲染次数:{{ renderCount }}</p>
</div>
</template>
<script setup>
import { shallowRef, watchEffect, ref } from 'vue';
const rObj = shallowRef({ a: 1, b: { c: 2 } });
const renderCount = ref(0);
// 在模板里渲染 rObj 会导致依赖收集一次
watchEffect(() => {
renderCount.value++;
// 访问 rObj(相当于访问 rObj.value),触发渲染计数
console.log('当前 rObj:', rObj.value);
});
// 修改内部字段(浅层不代理)
function updateNested() {
rObj.value.b.c = 3; // Vue 不会检测到,不会 re-render
console.log('已修改内部 b.c,但没有触发渲染');
}
// 替换整个对象(顶层变化会触发依赖更新)
function replaceObj() {
rObj.value = { a: 10, b: { c: 20 } }; // 触发渲染
console.log('已替换整个对象,触发渲染');
}
</script>
运行逻辑(预期):
- 首次加载时会渲染一次
rObj
,renderCount = 1
。 - 点击 “修改内部属性”,虽然
rObj.value.b.c
被改为3
,但不触发 watchEffect 中的渲染,renderCount
保持不变。 - 点击 “替换整个对象”,
rObj.value = …
会触发 watchEffect,renderCount
+1。
5.3 内部原理剖析(图解)
┌──────────────────────────────────────────┐
│ shallowRef({ a: 1, b: { c: 2 } }) │
└────────────────┬─────────────────────────┘
│ 创建一个 ShallowRef 对象
┌──────────────▼──────────────┐
│ ShallowRefImpl { │
│ __v_isRef: true │
│ _dirty: true │
│ // value 指向原始对象 │
│ _value: { a:1, b:{ c:2 }} │ <-- 这是原始对象,无 Proxy
│ effect: 对应依赖收集容器 │
│ } │
└──────────────┬──────────────┘
│ 访问 r.value 时,收集 effect
┌──────────────▼──────────────┐
│ 模板或 watchEffect() 中访问 │
│ console.log(rObj.value) │
└──────────────┬──────────────┘
│ 如果执行 rObj.value = newObj,则触发 effect
┌──────────────▼─────────────────────────┐
│ 替换顶层对象 newObj 时,触发 ShallowRefImpl.trigger() │
│ —— watchEffect 或模板重新渲染 —— │
└─────────────────────────────────────────┘
- 获取(get)行为: 只对
.value
本身做依赖收集。 - 设置(set)行为: 只有当你给
r.value
整体赋新的对象/值时,才会触发依赖更新。 - 内部嵌套对象
b: { c: 2 }
未被代理,直接就是普通对象,访问或修改时不会触发 Vue 的响应式系统。
5.4 使用场景与注意事项
使用场景:
- “只关心整体替换”:比如缓存了一份外部传入的第三方对象,仅需在整个对象换成新引用时触发视图更新。
- 大型深度嵌套对象,不想为其内部每一层都做响应式代理,只需某些顶层属性变化时渲染。
- 逐步迁移:从普通对象快速升级为响应式状态时,先用
shallowRef
保证最外层可控。
注意事项:
- 不会拦截内部字段修改,如果你误以为浅层 Ref 会对嵌套对象生效,可能会导致界面无法更新。
- 如果想对内部某些字段做响应式,可手动把内部对象包裹成
reactive
或ref
。 - 在模板里直接写
{{ rObj.b.c }}
时,仍然是访问原始对象b
,不会触发响应式。
shallowReactive 详解
6.1 基本概念与语法
import { shallowReactive } from 'vue';
shallowReactive(object)
会返回一个 Proxy 实例,对传入的object
顶层属性进行拦截(get/ set),但不会递归地将object
的嵌套对象做响应式转换。
示例:
const state = shallowReactive({ x: 1, nested: { y: 2 } });
// 访问 state.x:触发依赖收集
// 修改 state.x:触发依赖更新
// 访问 state.nested:读取的是原始对象 { y: 2 }(未代理)
// 修改 state.nested.y:Vue 无法检测,无法触发依赖更新
6.2 代码示例
<template>
<div>
<h3>shallowReactive 示例</h3>
<p>state.x: {{ state.x }}</p>
<p>state.nested.y: {{ state.nested.y }}</p>
<button @click="updateX">修改 x</button>
<button @click="updateNestedY">修改 nested.y</button>
<p>渲染次数:{{ renderCount }}</p>
</div>
</template>
<script setup>
import { shallowReactive, watchEffect, ref } from 'vue';
const state = shallowReactive({ x: 1, nested: { y: 2 } });
const renderCount = ref(0);
// 依赖 state.x 和 state.nested.y
watchEffect(() => {
renderCount.value++;
console.log('state.x:', state.x, 'nested.y:', state.nested.y);
});
// 修改顶层 x
function updateX() {
state.x += 1; // 触发渲染
console.log('已修改 state.x');
}
// 修改嵌套属性 nested.y
function updateNestedY() {
state.nested.y += 1; // 不触发渲染(nested 未被代理)
console.log('已修改 state.nested.y,但不会触发渲染');
}
</script>
运行逻辑(预期):
- 首次加载时,
watchEffect
访问state.x
与state.nested.y
,触发一次渲染,renderCount = 1
。 - 点击 “修改 x” 时,
state.x
变化,触发watchEffect
,renderCount = 2
。 - 点击 “修改 nested.y” 时,Vue 无法检测到
nested
对象内部修改,不会触发watchEffect
,renderCount
保持不变。
6.3 内部原理剖析(图解)
shallowReactive({ x:1, nested: { y:2 } })
└─> 创建一个 Proxy 对象,handler 只拦截第一层属性
┌───────────────────────────────────────────┐
│ Proxy( │
│ target: { x:1, nested:{ y:2 } }, │
│ handler: { │
│ get(target, key) { │
│ // 访问顶层属性时收集依赖 │
│ return Reflect.get(target, key) │
│ }, │
│ set(target, key, newVal) { │
│ // 修改顶层属性时触发依赖 │
│ return Reflect.set(target, key, newVal) │
│ } │
│ } │
│ ) │
└───────────────────────────────────────────┘
↑ ↑
│ └─ 访问/修改 “nested” 只是拿到原始对象,没有做递归代理
│
└─ 访问/修改 “x” 时:收集/触发依赖
- Proxy handler 只拦截第一层,访问或更改
state.x
时,会依次执行get
/set
,并进行依赖收集或触发。 - 访问
state.nested
:直接拿到原始对象{ y: 2 }
,Vue 不会为其创建新的 Proxy,也不会收集与触发依赖。
6.4 使用场景与注意事项
使用场景:
- 顶层字段变化触发视图时足够,无需对子属性再做局部响应式。
- 数据源来自外部库,不便或不需要修改其内部细节,只想拦截最外层键值。
- 避免深度代理带来的递归性能开销,尤其在大型对象场景下。
注意事项:
- 凡是访问或修改
nested
内部字段时,均不会触发 Vue 的响应式系统。 - 如果你需要在某个属性值变化时,对其内部某个字段进行响应式拦截,需手动对该字段做
reactive
或再包装一层shallowReactive/reactive
。 - 在模板中取
{{ state.nested.y }}
,会正常显示y
的最新值,但当nested.y
改变时,模板不会重新渲染,除非你重新给state.nested = {...}
(顶层重新赋值)或触发对nested
的引用更改。
- 凡是访问或修改
shallowReadonly 详解
7.1 基本概念与语法
import { shallowReadonly } from 'vue';
shallowReadonly(object)
会返回一个 Proxy,与readonly(object)
类似,但只对传入对象的顶层属性进行“只读”保护,对内部嵌套对象不作递归处理。- 差异在于: 当尝试修改顶层属性时,会发出警告;但修改嵌套属性时不会被拦截,依然可以成功赋值,不会提示只读错误。
示例:
const data = shallowReadonly({ a: 1, nested: { b: 2 } });
// 访问 data.a:正常读取
// 修改 data.a = 2:会在开发模式下 console.warn(“Set operation failed: target is readonly.”)
// 访问 data.nested:得到原始对象 { b:2 }(未被递归只读包装)
// 修改 data.nested.b = 3:没有只读保护,内部数据实际上被修改了
7.2 代码示例
<template>
<div>
<h3>shallowReadonly 示例</h3>
<p>data.a: {{ data.a }}</p>
<p>data.nested.b: {{ data.nested.b }}</p>
<button @click="modifyATop">尝试修改 data.a</button>
<button @click="modifyNestedB">修改 data.nested.b</button>
<p>注意:控制台将输出警告或正常修改</p>
</div>
</template>
<script setup>
import { shallowReadonly, ref } from 'vue';
const data = shallowReadonly({ a: 1, nested: { b: 2 } });
function modifyATop() {
data.a = 10; // 顶层只读,会在控制台输出警告
console.log('尝试修改 data.a=', data.a);
}
function modifyNestedB() {
data.nested.b = 20; // 嵌套对象没有被只读保护,可以正常修改
console.log('已修改 data.nested.b=', data.nested.b);
}
</script>
运行逻辑(预期):
- 点击 “尝试修改 data.a” 时,Vue 会在控制台输出警告,
data.a
保持原始值1
。 - 点击 “修改 data.nested.b” 时,
data.nested.b
可以成功被赋值为20
,并且模板也会立刻展示为20
(因为对嵌套对象不是只读或响应式拦截,仅仅是普通对象,所以修改后在模板里渲染时会实时读取最新值)。
7.3 内部原理剖析(图解)
shallowReadonly({ a:1, nested:{ b:2 } })
└─> 创建一个 Proxy,仅处理第一层属性的 set 操作
┌──────────────────────────────────────────┐
│ Proxy( │
│ target: { a:1, nested:{ b:2 } }, │
│ handler: { │
│ get(target, key) { │
│ return Reflect.get(target, key) │
│ }, │
│ set(target, key, value) { │
│ // 尝试修改 a,会发出只读警告,返回 false │
│ // 修改 nested,则由于 handler 只拦截第一层, │
│ // Reflect.set 操作依然会被执行 │
│ console.warn('Set operation failed: target is readonly.') │
│ return false; │
│ } │
│ } │
│ ) │
└──────────────────────────────────────────┘
↑ ↑
│ └─ 当 key = "nested" 时,set 操作会被 Reflect.set 执行
│ (Vue 默认开发模式下只在顶层抛出警告,但不阻止嵌套对象修改)
└─ 当 key = "a" 时,触发只读警告,返回 false,阻止赋值
- 访问(get):和普通对象一致,不拦截二级访问。
- 修改顶层属性(set):拦截并发出警告(开发模式)、返回
false
(strict
模式下可能会抛错)。 - 修改嵌套属性:因为 handler 只对第一层属性
key
做 set 拦截,实际修改会直接调用底层的原始对象赋值,不会被阻止,也不会触发警告。
7.4 使用场景与注意事项
使用场景:
- 保护状态顶层字段 不被误改,特别是在组件之间需要只允许读取但不允许修改的场景。
- 配置对象:顶层字段决定业务逻辑走向,而嵌套字段可以允许自由修改。
- 第三方传入只读要求:有时候库希望暴露一个只读面向外部的对象,但内部属性依然可以由开发者自行修改。
注意事项:
- 只对顶层生效,若你误以为整个对象都“只读”,会导致异常:深层属性仍然可写。
- 在严格模式(
use strict
)下,Vue 会在修改顶层属性时抛出错误,而不是仅仅警告。 - 如果需要深度只读,还是要使用普通的
readonly
,它会递归对所有层级都保护。
综合示例:三种浅层 API 联合使用
下面,我们通过一个综合示例,模拟一个“浅层缓存 + 浅层状态 + 浅层配置”场景,演示如何在一个组件里同时使用 shallowRef
、shallowReactive
和 shallowReadonly
。
<template>
<div>
<h2>综合示例:浅层 API 联合使用</h2>
<!-- 1. shallowRef:缓存异步数据 fetchedData -->
<div>
<h3>shallowRef(异步缓存示例)</h3>
<button @click="fetchData">Fetch Data</button>
<div v-if="fetchedData">
<p>fetchedData === rawData: {{ fetchedData === rawData }}</p>
<p>fetchedData.id: {{ fetchedData.id }}</p>
<p>fetchedData.nested.value: {{ fetchedData.nested.value }}</p>
<button @click="modifyFetchedNested">尝试修改 fetchedData.nested.value</button>
<p>修改后 fetchedData.nested.value: {{ fetchedData.nested.value }}</p>
</div>
</div>
<hr />
<!-- 2. shallowReactive:只对顶层 properties 处理 -->
<div>
<h3>shallowReactive(顶层追踪示例)</h3>
<p>state.count: {{ state.count }}</p>
<p>state.nested.msg: {{ state.nested.msg }}</p>
<button @click="state.count++">state.count++</button>
<button @click="state.nested.msg = '已修改'">修改 state.nested.msg</button>
</div>
<hr />
<!-- 3. shallowReadonly:顶层只读示例 -->
<div>
<h3>shallowReadonly(顶层只读示例)</h3>
<p>config.apiUrl: {{ config.apiUrl }}</p>
<p>config.options.flag: {{ config.options.flag }}</p>
<button @click="tryModifyApiUrl">尝试修改 config.apiUrl</button>
<button @click="config.options.flag = !config.options.flag">修改 config.options.flag</button>
</div>
</div>
</template>
<script setup>
import { shallowRef, shallowReactive, shallowReadonly, ref } from 'vue';
// === 1. shallowRef:模拟异步获取“大对象”然后缓存 ===
// 假设 rawData 是一个从后端获取的深度嵌套对象
const rawData = { id: 100, nested: { value: '初始' } };
// 用 shallowRef 来缓存 rawData
const fetchedData = shallowRef(null);
function fetchData() {
// 模拟异步 fetch
setTimeout(() => {
fetchedData.value = rawData; // 顶层赋值触发响应式
}, 500);
}
function modifyFetchedNested() {
if (fetchedData.value) {
// 由于是 shallowRef,fetchedData.value.nested.value 直接修改,但不触发任何响应式
fetchedData.value.nested.value = '浅层修改';
}
}
// === 2. shallowReactive:只对顶层属性追踪 ===
const state = shallowReactive({
count: 0,
nested: { msg: '原始消息' }
});
// count 变化时会触发视图更新,nested.msg 变化则不会
// === 3. shallowReadonly:顶层属性只读,嵌套属性可写 ===
const config = shallowReadonly({
apiUrl: 'https://api.example.com',
options: { flag: false }
});
function tryModifyApiUrl() {
// 尝试修改顶层字段 apiUrl,会触发只读警告,且值保持不变
config.apiUrl = 'https://evil.example.com';
}
</script>
示例解析:
shallowRef 场景:
fetchedData
初始为null
,点击 “Fetch Data” 后 500ms 将rawData
赋给fetchedData.value
,触发视图更新。- 点击 “尝试修改 fetchedData.nested.value”,会将
rawData.nested.value
修改为'浅层修改'
,但由于是浅层 Ref,模板中 不会 自动刷新nested.value
。 - 你可以在控制台验证:
fetchedData.value.nested.value
实际上被改了,但模板未重新渲染。
shallowReactive 场景:
state.count
改变时,Vue 能检测到并触发重新渲染。state.nested.msg
改变时,Vue 无法检测到,也不会重新渲染该节点,因为nested
对象未做深度代理。
shallowReadonly 场景:
- 顶层字段
config.apiUrl
是只读,tryModifyApiUrl
会在控制台输出警告,但config.apiUrl
保持原始值。 - 嵌套属性
config.options.flag
非顶层,仍然可以被正常修改,模板也会实时显示最新值。
- 顶层字段
常见误区与解答
误区:
shallowRef
会对子属性做响应式- 实际情况:
shallowRef
只对.value
本身做响应式,内部属性不代理。若需要对子属性做响应式,请手动将子属性包装为ref
或reactive
。
- 实际情况:
误区:
shallowReactive
会阻止对内部对象的修改触发视图,但无法直接感知或警告- 实际情况:
shallowReactive
根本不会给嵌套对象套 Proxy,在代码里直接修改nested
内部时,不会触发视图,也不会抛出警告或错误。植入 watch 时也感知不到内部变化。
- 实际情况:
误区:
shallowReadonly
能够保证整个对象只读,子属性无法修改- 实际情况:
shallowReadonly
仅拦截一级字段的set
操作,对二级及以下字段不做任何拦截。内部仍然是普通对象,可以随意修改。
- 实际情况:
误区:使用“浅层”就不会影响性能
- 实际情况:浅层 API 能减少递归开销,但依然需要对顶层做 Proxy,依赖收集与触发也存在成本。如果项目本身数据量中等(几 MB 以下),普通
reactive
性能已经足够好,削减深度代理的优势可能并不显著。
- 实际情况:浅层 API 能减少递归开销,但依然需要对顶层做 Proxy,依赖收集与触发也存在成本。如果项目本身数据量中等(几 MB 以下),普通
误区:浅层 API 会将嵌套对象提升为非响应式
- 实际情况:浅层 API 只是不再对嵌套对象做 Proxy,但如果嵌套对象本身是通过
reactive
/ref
创建的,那么它仍然是响应式。
const nestedReactive = reactive({ y: 2 }); const state = shallowReactive({ nested: nestedReactive }); // state.nested 指向已响应式的 nestedReactive // 改变 nestedReactive.y 时,会触发视图更新 // 但如果 nested 直接是普通对象,则内部字段不会被代理
- 实际情况:浅层 API 只是不再对嵌套对象做 Proxy,但如果嵌套对象本身是通过
总结
shallowRef:
- 只拦截顶层
.value
的读写,内部对象不做深度响应式代理。 - 适用于“只关心整体替换是否变化”的场景。
- 只拦截顶层
shallowReactive:
- 只对对象最外层属性做 Proxy,内部嵌套保留为普通对象。
- 适用于“只需粗略感知顶层字段变化”、且想减少深度递归开销的场景。
shallowReadonly:
- 只对顶层字段做只读保护,内部嵌套对象依然可写。
- 适用于“顶层配置或状态禁止修改,嵌套字段可自由变更”的场景。
在实际项目中,当你的状态对象非常庞大或深度嵌套,并且对内部字段的响应式需求不高时,使用浅层响应式 API 可以大幅降低代理、依赖收集的开销,同时在写法上更加直观。如果你需要对子属性进行精细化追踪,仍然可以将内部对象再手动做 reactive
或 ref
包裹,从而兼顾性能与灵活性。
希望这篇《Vue 3 浅层响应式 API 全解析:shallowRef、shallowReactive 与 shallowReadonly 深度探索》能让你在理解“浅层响应式”概念、掌握 API 用法、并结合实际应用场景上更加游刃有余。祝你在 Vue 3 的响应式世界里,写出性能与可维护并存的高质量代码!
评论已关闭