非原始值响应式方案(五)

浅响应与深响应

本节中我们将要介绍reactiveshallowReactive的区别,即浅响应与深响应的区别。实际上,我们目前所实现的reactive是浅响应的。拿如下代码来说:

1
2
3
4
5
6
const obj = reactive({ foo: { bar: 1 } })

effect(() => {
console.log(obj.foo.bar)
})
obj.foo.bar = 2

首先,创建 obj 代理对象,该对象的 foo 属性值也是一个对象,即{bar: 1}。接着,在副作用函数内部访问 obj.foo.bar 的值。但是我们发现,后续对 obj.foo.bar 的修改不能触发副作用函数重新执行,这是为什么呢?我们来看一下现在的实现:

1
2
3
4
5
6
7
8
9
10
11
12
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
if (key === 'raw') {
return target
}
track(target, key)

return Reflect.get(target, key, receiver)
},
})
}

由上面这段代码可知,我们在读取 obj.foo.bar 时,首先要读取 obj.foo 的值。这里我们直接使用Reflect.get函数返回 obj.foo 的结果。由于通过 Reflect.get 得到 obj.foo 的结果是一个普通对象,即{ bar: 1 },它并不是一个响应式对,所以在副作用函数中访问 obj.foo.bar 时,是不能建立响应联系的。要解决这个问题,我们需要对Reflect.get返回的结果做一层包装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
if (key === 'raw') {
return target
}
track(target, key)
// 得到原始值结果
const res = Reflect.get(target, key, receiver)
if (typeof res === 'object' && res !== null) {
// 调用reactive将结果包装成响应式数据并返回
return reactive(res)
}
// 返回res
return res
},
})
}

如上代码所示,当读取属性值时,我们首先检查该值是否是对象,如果是对象,则递归调用reactive函数将其包装成响应式数据并返回。这样当使用 obj.foo 读取 foo 属性值时,得到的就会是一个响应式数据,因此再通过 obj.foo.bar 读取 bar 属性值时,自然就会建立联系。这样,当修改 obj.foo.bar 的值时,就能够触发副作用函数重新执行了。

然而,并非所有情况下我们都希望深响应,这就催生了shallowReactive,即浅响应。所谓浅响应,指的是只有对象的第一层属性是响应的,例如:

1
2
3
4
5
6
7
8
9
const obj = shallowReactive({ foo: { bar: 1 } })

effect(() => {
console.log(obj.foo.bar)
})
// obj.foo是响应的,可以触发副作用函数重新执行
obj.foo = { bar: 2 }
// obj.foo.bar不是响应的,不能触发副作用函数执行
obj.foo.bar = 3

在这个例子中,我们使用shallowReactive函数创建了一个浅响应的代理对象 obj。可以发现,只有对象的第一层属性是响应的,第二层及更深层次的属性则不是响应的。实现此功能也不难,如下面的代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function createReactive(obj, isShallow = false) {
return new Proxy(obj, {
// 拦截读取操作
get(targt, key, receiver) {
// 如果访问的属性是raw我们把原始值返回
if (key === 'raw') {
return target
}

// 得到原始值返回结果
const res = Reflect.get(target, key, receiver)

// 如果是浅响应,我们直接返回原始值
if (isShallow) {
return res
}
// 追踪依赖
track(target, key)
// 如果原始值结果还是一个对象,我们递归调用reactive方法对其进行一层包装使其变为相应数据
if (typeof res === 'object' && res !== null) {
// 调用reactive将结果包装成响应式数据并返回
return reactive(res)
}
// 返回res
return res
},
})
}

在上面这段代码中,我们把创建对象的工作封装到一个新的函数createReactive中。该函数除了接收原始对象 obj 以外,还接收第二个参数 isShallow,它是一个布尔值,代表是否创建浅响应对象。默认情况下,isShallow 的值为 false,代表创建深响应对象。这里需要注意的是,当读取属性操作发生时,在 get 拦截函数内如果发现是浅响应的,那么直接返回原始数据即可。有了createReactive函数后,我们可以使用它轻松的实现 reactive 以及 shallowReactive 函数了:

1
2
3
4
5
6
7
function reactive(obj) {
return createReactive(obj)
}

function shallowReactive(obj) {
return createReactive(obj, true)
}

代码

此章节修改的代码不多,主要是在reactive.js中。如下面代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/**
* 创建响应式对象
* @param {*} obj 原始对象
* @param {*} isShallow 是否浅创建响应式对象
* @returns
*/
function createReactive(obj, isShallow = false) {
return new Proxy(obj, {
// 拦截读取操作
get(target, key, receiver) {
// 如果访问的属性是raw我们把原始值返回
if (key === 'raw') {
return target
}

// 得到原始值返回结果
const res = Reflect.get(target, key, receiver)

// 如果是浅响应,我们直接返回原始值
if (isShallow) {
return res
}
// 追踪依赖
track(target, key)
// 如果原始值结果还是一个对象,我们递归调用reactive方法对其进行一层包装使其变为相应数据
if (typeof res === 'object' && res !== null) {
// 调用reactive将结果包装成响应式数据并返回
return reactive(res)
}
// 返回res
return res
},
// 拦截in操作符 'name' in p
has(target, key) {
track(target, key)
return Reflect.has(target, key)
},
ownKeys(target) {
track(target, ITERATE_KEY)
return Reflect.ownKeys(target)
},
set(target, key, newVal, receiver) {
// 先获取旧值
const oldVal = target[key]
// type看看操作类型是添加新属性还是设置已有属性
const type = Object.prototype.hasOwnProperty.call(target, key)
? 'SET'
: 'ADD'
// 拿到执行结果
const res = Reflect.set(target, key, newVal, receiver)
// 如果两者相等,说明receiver是target的代理对象
if (target === receiver.raw) {
if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
trigger(target, key, type)
}
}
return res
},
deleteProperty(target, key) {
// 检查被操作的属性是否是对象自身的属性
const hadKey = Object.prototype.hasOwnProperty.call(target, key)
// 使用Reflect.deleteProperty完成属性的删除
const res = Reflect.deleteProperty(target, key)

if (res && hadKey) {
// 只有当被删除的属性是自身属性且成功被删除时,才触发更新
trigger(target, key, 'DELETE')
}
// 返回结果
return res
}
})
}
function reactive(obj) {
return createReactive(obj)
}

function shallowReactive(obj) {
return createReactive(obj, true)
}