
ref
与 reactive
都属于深层响应 (deep) API。它们会递归地将内部所有嵌套对象都转换为响应式代理。在多数情况下这非常方便,但当处理大型数据结构时,这种深度监听的性能开销可能会造成瓶颈。这时候我们就会使用到 shallowRef
或 shallowReactive
。
ShallowRef
shallowRef
会创建一个 ref,但只对 .value
属性本身的赋值操作具有响应性,它并不会将 .value
的内容(如果是对象)转换为响应式对象。
HTML
<body>
<div id="app"></div>
<script type="module">
import { shallowRef, effect } from '../../../node_modules/vue/dist/vue.esm-browser.js'
// import { reactive, effect, toRefs } from '../dist/reactivity.esm.js'
const c = shallowRef({
count: 1
});
effect(() => {
console.log(c.value.count)
})
setTimeout(() => {
c.value.count++ // 这一行不会触发 effect
}, 1000)
</script>
</body>
查看上述代码,你会观察到 effect
只输出一次。一秒钟后 setTimeout
执行了 c.value.count++
,但这并没有触发 effect
再次执行。
如果希望 effect
再次执行,必须修改 c.value
本身,例如改为 c.value = { count: 2 }
。


依照惯例,我们 console.log(c)
,可以看到它是一个 RefImpl
类的实例,但差异在于 __v_isShallow
标记为 true
。由此我们可以知道 ref
和 shallowRef
复用了相同的类,只是通过这个标记来告知 RefImpl
是否需要进行深层响应。
TypeScript
// ref.ts
// 如果 value 是对象,则使用 reactive 转换为响应式对象
const convert = (value) => isObject(value) ? reactive(value) : value
class RefImpl implements Dependency {
_value;
[ReactiveFlags.IS_REF] = true
private _isShallow: boolean // 增加一个私有标记
subs: Link
subsTail: Link
constructor(value, isShallow: boolean) {
this._isShallow = isShallow // 保存标记
// 如果是 shallow,直接使用原始值;否则,进行 convert 转换
this._value = isShallow ? value : convert(value)
}
get value() {
if (activeSub) {
trackRef(this)
}
return this._value
}
set value(newValue) {
// 如果新值和旧值发生过变化,则更新
if (hasChanged(newValue, this._value)) {
// 如果是 shallow,直接使用新值;否则,进行 convert 转换
this._value = this._isShallow ? newValue : convert(newValue)
triggerRef(this)
}
}
}
所以我们的实现思路是:
- 帮
RefImpl
类增加一个私有的_isShallow
属性。 - 将"检查值是否为对象,若是则用
reactive
转换"的逻辑封装成convert
函数。 - 在
constructor
和setter
中,根据_isShallow
标记来决定是直接赋值,还是要经过convert
函数处理。
ShallowReactive
shallowReactive
只对对象的第一层属性进行代理,任何对嵌套对象的修改都不会触发响应。
HTML
<body>
<div id="app"></div>
<script type="module">
import { shallowReactive, effect } from '../../../node_modules/vue/dist/vue.esm-browser.js'
// import { reactive, effect, toRefs } from '../dist/reactivity.esm.js'
const state = shallowReactive({
a: 1,
b: {
c: 1
}
})
effect(() => {
console.log(state.b.c)
})
setTimeout(() => {
state.b.c++ // 这一行不会触发 effect
}, 1000)
</script>
</body>
shallowReactive
也是一样的效果,state.b.c++
没有反应。需要将修改变为 state.b = { c: 2 }
,effect
才会触发输出。
reactive
是通过 mutableHandlers
来处理的,因此我们需要先重构 mutableHandlers
:
TypeScript
// baseHandlers.ts
// 提取 set
const set = (target, key, newValue, receiver) => {
// ...
}
// 提取 get
const get = (target, key, receiver) => {
track(target, key)
const res = Reflect.get(target, key, receiver)
if (isRef(res)) {
return res.value
}
if (isObject(res)) {
// 默认是深层响应,递归调用 reactive
return reactive(res)
}
return res
}
export const mutableHandlers = {
get,
set
}
我们将 get
的逻辑抽离成一个工厂函数 createGetter
,并用 isShallow
参数来控制是否要递归地将嵌套对象转换为 reactive
。
TypeScript
// baseHandlers.ts
function createGetter(isShallow = false) {
return function get(target, key, receiver) {
track(target, key)
const res = Reflect.get(target, key, receiver)
if (isRef(res)) {
return res.value
}
if (isObject(res)) {
// 关键:如果是 shallow,直接返回原始对象,不再递归调用 reactive
return isShallow ? res : reactive(res)
}
return res
}
}
// 创建深层 getter
const get = createGetter()
// 创建浅层 getter
const shallowGet = createGetter(true)
// ... (set 逻辑不变)
export const mutableHandlers = {
get,
set
}
export const shallowReactiveHandlers = {
get: shallowGet, // 使用浅层 getter
set
}
创建了不同的 handler
并导出后,我们在 reactive.ts
中引入它们。
TypeScript
// reactive.ts
import { mutableHandlers, shallowReactiveHandlers } from './baseHandlers'
// 缓存 reactive 创建的代理
const reactiveMap = new WeakMap()
// 新增 shallowReactive 的缓存
const shallowReactiveMap = new WeakMap()
// 重构 createReactiveObject,使其可以接收 handlers 和 map
function createReactiveObject(target, handlers, proxyMap) {
if (!isObject(target)) return target
// ... (检查是否已存在代理的逻辑)
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 根据传入的 handlers 创建代理
const proxy = new Proxy(target, handlers)
// 存储 target 和响应式对象的关联关系
proxyMap.set(target, proxy)
// reactiveSet.add(proxy) // (注:原文有 reactiveSet,这里简化为只用 map)
return proxy
}
export function reactive(target) {
return createReactiveObject(target, mutableHandlers, reactiveMap)
}
// 新增 shallowReactive 函数
export function shallowReactive(target) {
return createReactiveObject(target, shallowReactiveHandlers, shallowReactiveMap)
}
这样我们就完成了 shallowReactive
。
"深层响应"为我们带来了开发上的便利,而"浅层响应"则让我们有进一步优化性能的机会。在一般开发过程中,我们应该优先使用 ref
和 reactive
,当遇到明确的性能瓶颈时,可以观察我们自己在使用数据的情况,适度地替换为 shallow
版本。
想了解更多 Vue 的相关知识,抖音、B站搜索我师父「远方os」,一起跟日安当同学。