reactive

reactive和ref的区别:

  • ref支持传入所有类型,reactive只支持引用类型(Array、Object、Map、Set、WeakSet、WeakMap等)
  • ref取值和赋值都要加.value,reactive不需要
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
<template>
<div>
<form>
<input v-model="form.name" type="text">
<br>
<input v-model="form.age" type="text">
<br>
<button @click.prevent="submit">提交</button>
</form>
</div>
<br>

<!-- --------------------------------------------------------- -->

<div>
<ul>
<li v-for="(item, index) in list" :key="index">{{ item }}</li>
<!-- <li v-for="(item, index) in list.arr" :key="index">{{ item }}</li> -->
</ul>
<button @click="add"></button>
</div>
</template>

<script setup lang="ts">
import { reactive } from "vue"

type P = {
name: string;
age: number;
}

// 只支持引用类型的参数,用基本类型就报错
// let form = reactive("")

// 和ref一样可以进行类型推断,也可以自己定义
// let form = reactive<P>({
// name: "yajue",
// age: 24
// })

let form = reactive({
name: "yajue",
age: 24
})

// 不需要.value
// form.age = 114514

const submit = ()=> {
console.log(form)
}

// --------------------------------------------------------------

// reactive是proxy代理的对象,直接赋值会覆盖掉它
let list = reactive<string[]>([])

// let list = reactive<{
// arr: string[]
// }>({
// arr: []
// })

const add = ()=> {
// 假设它是接口返回的数据
setTimeout(() => {
let res = ["www", "草", "kusa"]
// 会破坏响应式对象,不能直接赋值
// list = res

// 解决方案1:可以用数组方法
list.push(...res)

// 解决方案2:把数组作为一个对象属性,并把对象赋给reactive
// list.arr = res

console.log(list)
}, 2000)
}
</script>

readonly

拷贝一份proxy对象,并将其设为只读。

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
<template>
<div>
<button @click="show"></button>
</div>
</template>

<script setup lang="ts">
import { reactive, readonly } from 'vue'

let form = reactive({
name: "yajue",
age: 24
})

// 把reactive所有属性变成只读
// 实际生产中用得不多,但源码里用得很多
const read = readonly(form)

const show = ()=> {
// 会报错,因为read是只读的
// read.name = "野兽"
// console.log(form, read) // -> {name:"yajue",age:24}, {name:"yajue",age:24}

// 不会报错,因为form不是只读的
// read会受原始对象影响
form.name = "野兽"
console.log(form, read) // -> {name:"野兽",age:24}, {name:"野兽",age:24}
}
</script>

shallowReactive

只有浅层的数据(第一层)才会变成响应式。

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
<template>
<div>
<div>reactive: {{ form }}</div>
<div>shallowReactive: {{ obj }}</div>
<button @click="edit"></button>
</div>
</template>

<script setup lang="ts">
import { reactive, readonly, shallowReactive } from 'vue'

let form = reactive({
name: "yajue",
age: 24
})

// shallowReactive 作浅层响应式
const obj = shallowReactive<any>({
foo: {
bar: {
num: 114514
}
}
})

const edit = ()=> {
// 改变第一层的属性可以响应
// obj.foo = { num = 1919810 }

// 改变深层嵌套的属性就不会引起视图的更新
// obj.foo.bar.num = 1919810
// console.log(obj)

// 和ref&shallowRef一样,reactive和shallowReactive同时写也会出现问题
form.name = "野兽"
obj.foo.bar.num = 1919810
}
</script>

源码

在Vue源码(/package/reactivity/src/reactive.ts)中可以看到reactive的源码。

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
// reactive使用了泛型约束,只支持引用类型的参数,所以用基本类型就报错
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
// 如果对象是只读的,就直接返回
if (isReadonly(target)) {
return target
}
// 否则调用createReactiveObject
return createReactiveObject(
target,
false, // isReadonly
mutableHandlers, // 用于给Array类型创建proxy
mutableCollectionHandlers, // 用于给Set Map WeakSet WeakMap类型创建proxy
reactiveMap
)
}

// --------------------------------------------------------------

function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
// WeakMap(弱映射),主要实现值与对象的关联而不导致内存泄漏
// WeakMap的键只能是Object类型的数据(null除外),且WeakMap的键名所指的对象不计入垃圾回收机制
// WeakMap的值可以是任意的
// 它的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内
// 因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占的内存
// 即,一旦不再需要,WeakMap里的键名对象和所对应的键值对会自动删除,不用手动删除引用
) {
// 如果传入基本类型,报错
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
// 如果target已经被代理了,且程序员的目的不是将响应式对象变成只读,则直接返回
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// target already has corresponding Proxy
// 从缓存(readonlyMap, reactiveMap)中查找,如果已经被代理就直接返回
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// only specific value types can be observed.
// 如果目标在白名单里直接返回,例如__skip__
// 通过markRaw函数处理的数据会加上__skip__,即跳过代理
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
//以上条件都没触发,开始做proxy代理
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
// 缓存新代理后的对象
proxyMap.set(target, proxy)
return proxy
}