第六章、原始值的响应式方案
Proxy 只能代理对象,无法对字符串之类的原始值进行代理,如果要代理这些原始值,需要做一层包装,包装成一个对象后再进行代理。
// 定义一个包装函数,将原始值包装成一个对象
function ref(val) {
const wrapper = {
value: val,
}
// 返回响应式对象
return reactive(wrapper)
}
const refVal = ref(1)
effect(() => {
console.log(refVal.value)
})
// 修改能够触发副作用函数执行
refVal.value = 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
需要一个方法来区别一个数据是否是 ref 对象,方便实现后面的自动脱 ref 功能。
function ref(val) {
const wrapper = {
value: val,
}
// 在wrapper上面定义一个不可枚举属性且不可写属性__v_isRef,表示其是一个ref对象
Object.defineProperty(wrapper, '__v_isRef', {
value: true,
})
return reactive(wrapper)
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
现在的响应式对象在使用扩展运算符的时候,会出现响应式丢失的情况,可以利用 ref 来解决这个问题。
const obj = reactive({ foo: 1, bar: 2 })
const newObj = {
...obj,
}
effect(() => {
console.log(newObj.foo)
})
// 不会触发响应,因为副作用函数内的newObj是一个普通对象,不会进行依赖收集,没有响应能力
obj.foo = 2
// -----------------------------------
// 定义一个函数来将属性转为响应式的
function toRef(obj, key) {
// 这个对象具有一个访问器属性value,当访问value时,本质相当于读取了响应式对象obj上面的属性
const wrapper = {
get value() {
return obj[key]
},
}
Object.defineProperty(wrapper, '__v_isRef', {
value: true,
})
return wrapper
}
const obj = reactive({ foo: 1, bar: 2 })
// 将newObj的属性改为通过toRef函数读取
const newObj = {
foo: toRef(obj, 'foo'),
bar: toRef(obj, 'bar'),
}
effect(() => {
console.log(newObj.foo.value)
})
// 能够触发响应
obj.foo = 2
// -----------------------------------
// 属性过多时,可以创建一个批量转换的函数来处理
function toRefs(obj) {
const ret = {}
// 遍历响应式对象上面的属性,将其转换为ref属性
for (const key in obj) {
ret[key] = toRef(obj, key)
}
return ret
}
const obj = reactive({ foo: 1, bar: 2 })
// 可以直接使用扩展运算符创建新的响应式对象了
const newObj = { ...toRefs(obj) }
effect(() => {
console.log(newObj.foo.value)
})
// 能够触发响应
obj.foo = 2
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
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
目前 toRef 方法返回的对象的 value 属性只有 get 访问器,没有 set 访问器,这会导致创建的 ref 对象变成只读的,需要实现 set 操作。
function toRef(obj, key) {
const wrapper = {
get value() {
return obj[key]
},
// 设置属性
set value(val) {
obj[key] = val
},
}
Object.defineProperty(wrapper, '__v_isRef', {
value: true,
})
return wrapper
}
const obj = reactive({ foo: 1, bar: 2 })
const newObj = toRef(obj, 'foo')
effect(() => {
console.log(newObj.value)
})
// 直接修改newObj,能够触发响应
newObj.value = 2
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
目前的 ref 属性需要通过 value 属性才能访问和修改,需要一种方式能直接通过属性本身访问和修改,类似于在 vue3 模板中可以直接写属性名称而不需要带.value。
// 定一个代理函数来实现自动脱勾ref
function proxyRefs(target) {
return new Proxy(target, {
// 代理读取属性
get(target, key, receiver) {
const value = Reflect.get(target, key, receiver)
// 通过判断之前设置的__v_isRef属性,如果是ref就返回.value,不是就直接返回
return value.__v_isRef ? value.value : value
},
set(target, key, newValue, receiver) {
const value = target[key]
// set操作时也一样,如果是ref,就设置.value,否则直接调用原来的Reflect.set
if (value.__v_isRef) {
value.value = newValue
return true
}
return Reflect.set(target, key, newValue, receiver)
},
})
}
const obj = reactive({ foo: 1, bar: 2 })
const newObj = proxyRefs({ ...toRefs(obj) })
// 能够直接访问属性
console.log(newObj.foo)
console.log(newObj.bar)
// 能够直接设置属性
newObj.foo = 2
console.log(newObj.foo)
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
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