Vue3 响应式模块源码剖析
ReactiveAPI
原理分析
一.export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
// 如果这个对象已经被 readonly代理过了,则直接返回。 被readonly代理过就会添加proxy,取值时会走get方法
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
return createReactiveObject( // 创建响应式对象
target,
false,
mutableHandlers,
mutableCollectionHandlers
)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>
) {
if (!isObject(target)) { // reactive只接受对象
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 如果被reactive 处理过的对象还可以继续被readonly处理
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// target already has corresponding Proxy 对象 和 代理 进行缓存
const proxyMap = isReadonly ? readonlyMap : reactiveMap
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// only a whitelist of value types can be observed.
const targetType = getTargetType(target) // 看对象是否是可扩展的
if (targetType === TargetType.INVALID) { // 不可扩展直接返回
return target
}
const proxy = new Proxy( // 核心 创建proxy, 对集合的处理和普通的对象略有不同
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy) // 缓存起来
return 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
30
31
32
33
34
35
36
37
38
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
createReactiveObject
实现分为几个步骤:
- 1.如果不是对象就直接返回。
- 2.如果目标对象已经被代理过,被
reactive
代理过的对象,还可以被readonly
进行包装 - 3.查看缓存中。看对象是否已经被代理过,如果代理过则直接返回
- 4.检测对象是否可扩展。
- 5.针对对象创建代理,并缓存代理对象
- 6.返回代理对象
调试方法:
暂且更改开发时打包的入口文件
scripts/dev.js
const target = args._.length ? fuzzyMatchTarget(args._)[0] : 'reactivity'
1
案例1:
const { reactive, readonly } = VueReactivity;
let obj = { name: 'zf' };
let proxy1 = readonly(obj);
debugger;
let proxy2 = reactive(proxy1);
console.log(proxy1 === proxy2);
1
2
3
4
5
6
2
3
4
5
6
被
readonly
代理过的对象,不能在使用reactive代理
案例2:
let obj = { name: 'zf' };
debugger
let proxy1 = reactive(obj);
let proxy2 = reactive(proxy1);
let proxy3 = reactive(obj);
1
2
3
4
5
2
3
4
5
对象不会被重复代理,同时如果已经是代理对象也不会再次进行代理
案例3:
let obj = { name: 'zf' };
let proxy = reactive(obj);
debugger
console.log(obj === toRaw(proxy));
let obj2 = { name: 'zf' };
let proxy2 = reactive(markRaw(obj2));
console.log(proxy2);
1
2
3
4
5
6
7
2
3
4
5
6
7
通过代理对象可以获取被代理数据,必要时可以使用
markRaw
标记对象,可以避免对象的代理操作
baseHandlers
实现
二.export const mutableHandlers: ProxyHandler<object> = {
get, // 访问属性代理
set, // 设置对象属性代理
deleteProperty, // 删除属性代理
has, // 访问in操作触发
ownKeys // 调用Object.getOwnPropertyNames()方法时触发
}
1
2
3
4
5
6
7
2
3
4
5
6
7
1).get访问器
// 不同类型的get 根据shallow readonly 进行判断生成
const get = /*#__PURE__*/ createGetter()
const shallowGet = /*#__PURE__*/ createGetter(false, true)
const readonlyGet = /*#__PURE__*/ createGetter(true)
const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true)
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
if (key === ReactiveFlags.IS_REACTIVE) { // 用来判断这个对象是reactive还是readonly
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) { // 如果取的是IS_READONLY
return isReadonly
} else if (
key === ReactiveFlags.RAW && // 可以使用toRaw方法获取被代理过对象对应的原值
receiver === (isReadonly ? readonlyMap : reactiveMap).get(target)
) {
return target
}
const targetIsArray = isArray(target)
// 如果是数组 对 'includes', 'indexOf', 'lastIndexOf' 方法进行处理
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
const res = Reflect.get(target, key, receiver)
if ( // 是内置symbol或者是原型链 查找到的,直接返回
isSymbol(key)
? builtInSymbols.has(key as symbol)
: isNonTrackableKeys(key)
) {
return res
}
if (!isReadonly) {
track(target, TrackOpTypes.GET, key) // 依赖收集
}
if (shallow) {
return res
}
if (isRef(res)) { // 数组访问索引时 无需.value
// ref unwrapping - does not apply for Array + integer key.
const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
return shouldUnwrap ? res.value : res // 直接返回value
}
if (isObject(res)) { // 如果是对象 递归处理
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
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
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
案例1:
let proxyArr = reactive([1, 2, 3]);
// 访问数组的方法时会访问数组的长度
proxyArr.push(5); // 调用数组方法时 有暂停收集的功能和增加收集项的功能
proxyArr[Symbol.hasInstance]; // 访问内置属性不会依赖收集
let r = reactive({
name: ref('zf')
});
console.log(r.name); // reactive 会判断里面是否包含ref,自动拆包
let r1 = reactive([ref(1), 2, 3, 4]); // 这种情况下不会拆包
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
2).set访问器
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
const oldValue = (target as any)[key]
if (!shallow) {
value = toRaw(value) // 如果设置的值是reactive过的 会被转化为普通对象
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value // 老的是ref 新的不是ref 则会给老的ref赋值
return true
}
} else {
// in shallow mode, objects are set as-is regardless of reactive or not
}
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
const result = Reflect.set(target, key, value, receiver)
// don't trigger if target is something up in the prototype chain of original
// 不触发原型链上的trigger
if (target === toRaw(receiver)) {
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value) // 新增
} else if (hasChanged(value, oldValue)) { // 修改
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
}
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
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
案例1:
let proxy1 = reactive({ name: 'zf', age: ref(11) });
proxy1.name = reactive({ str: 'jw' });
proxy1.age = ref(12);
console.log(proxy1);
1
2
3
4
2
3
4
如果设置的值是reactive或者ref的情况
案例2:
let obj = {};
let proto = {a:1}
let proxyProto = new Proxy(proto, {
get(target,key,receiver) {
return Reflect.get(target,key,receiver)
},
set(target,key,value,receiver){
console.log(proxyProto , receiver == myProxy)
return Reflect.set(target,key,value,receiver)
}
})
Object.setPrototypeOf(obj,proxyProto); //原型链
let myProxy = new Proxy(obj,{
get(target,key,receiver) {
return Reflect.get(target,key,receiver)
},
set(target,key,value,receiver){
console.log(receiver === myProxy)
return Reflect.set(target,key,value,receiver)
}
})
myProxy.a = 100
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
原型链也是一个proxy,那么通过
Reflect.set
方法设置值,会触发原型的set方法
小作业:
尝试分析下
baseHandlers
中其他的访问器
collectionHandlers
实现
三.// 集合中的方法 map.get map.set, 最终都会调用get方法
export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {
get: createInstrumentationGetter(false, false)
}
export const shallowCollectionHandlers: ProxyHandler<CollectionTypes> = {
get: createInstrumentationGetter(false, true)
}
export const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {
get: createInstrumentationGetter(true, false)
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
const instrumentations = shallow // 返回不同的getter
? shallowInstrumentations
: isReadonly
? readonlyInstrumentations
: mutableInstrumentations
return (
target: CollectionTypes,
key: string | symbol,
receiver: CollectionTypes
) => {
if (key === ReactiveFlags.IS_REACTIVE) { // 处理 isReactive isReadonly toRaw
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
} else if (key === ReactiveFlags.RAW) {
return target
}
return Reflect.get( // 对map 、 set的方法进行处理
hasOwn(instrumentations, key) && key in target
? instrumentations
: target,
key,
receiver
)
}
}
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
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
const mutableInstrumentations: Record<string, Function> = {
get(this: MapTypes, key: unknown) {
return get(this, key)
},
get size() {
return size((this as unknown) as IterableCollections)
},
has,
add,
set,
delete: deleteEntry,
clear,
forEach: createForEach(false, false)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
set、map默认的方法进行代理
1).get访问器
function get( // 取值操作
target: MapTypes,
key: unknown,
isReadonly = false,
isShallow = false
) {
// #1772: readonly(reactive(Map)) should return readonly + reactive version
// of the value
target = (target as any)[ReactiveFlags.RAW]
const rawTarget = toRaw(target)
const rawKey = toRaw(key)
if (key !== rawKey) { // key可能是对象也是被proxy过的
!isReadonly && track(rawTarget, TrackOpTypes.GET, key)
} // 对rawKey进行依赖收集
!isReadonly && track(rawTarget, TrackOpTypes.GET, rawKey)
const { has } = getProto(rawTarget) // 获取has方法
const wrap = isReadonly ? toReadonly : isShallow ? toShallow : toReactive
if (has.call(rawTarget, key)) { // 是否包含key
return wrap(target.get(key)) // 如果是取到的值是对象接着代理
} else if (has.call(rawTarget, rawKey)) {
return wrap(target.get(rawKey))
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2).add访问器
function add(this: SetTypes, value: unknown) {
value = toRaw(value) // 添加的值转为普通值
const target = toRaw(this)
const proto = getProto(target) // 获取目标原型
const hadKey = proto.has.call(target, value) // 是否有此value
target.add(value) // 添加值
if (!hadKey) { // 没有则触发add
trigger(target, TriggerOpTypes.ADD, value, value)
}
return this
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
针对Set 处理,如果新增的才需要触发更新,已经有的就无所谓了。
3).set访问器
function set(this: MapTypes, key: unknown, value: unknown) {
value = toRaw(value)
const target = toRaw(this)
const { has, get } = getProto(target)
let hadKey = has.call(target, key) // 是否有key
if (!hadKey) {
key = toRaw(key)
hadKey = has.call(target, key)
} else if (__DEV__) {
checkIdentityKeys(target, has, key)
}
const oldValue = get.call(target, key)
target.set(key, value) // 添加数据
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value) // 触发更新 -》 添加逻辑
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue) // 触发修改
}
return this
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
针对map的操作
四.effect函数剖析
1) effect函数实现
const effectStack: ReactiveEffect[] = [] // 创建effect栈 包装effect执行顺序正确
let activeEffect: ReactiveEffect | undefined // 当前正在执行的effect
export function effect<T = any>(
fn: () => T,
options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
if (isEffect(fn)) { // 是否已经是effect,是effect获取对应的原函数
fn = fn.raw
}
const effect = createReactiveEffect(fn, options) // 创建响应式effect
if (!options.lazy) { // 是不是立即执行
effect()
}
return effect
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let uid = 0
function createReactiveEffect<T = any>(
fn: () => T,
options: ReactiveEffectOptions
): ReactiveEffect<T> {
const effect = function reactiveEffect(): unknown {
if (!effect.active) { // 如果不是active的,默认为true
return options.scheduler ? undefined : fn()
}
if (!effectStack.includes(effect)) {
cleanup(effect) // 每次重新收集依赖
try {
enableTracking() // shouldTrack = true
effectStack.push(effect)
activeEffect = effect
return fn()
} finally {
effectStack.pop()
resetTracking()
activeEffect = effectStack[effectStack.length - 1]
}
}
} as ReactiveEffect
effect.id = uid++ // effect 唯一标识
effect.allowRecurse = !!options.allowRecurse // 运行effect重复执行
effect._isEffect = true // effect是不是effect
effect.active = true // 是否激活
effect.raw = fn // 对应的原函数
effect.deps = [] // effect 对应的属性
effect.options = options
return effect
}
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
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
案例1:
// 1.已经是effect函数,再被effect
let reactiveEffect = effect(()=>{
console.log(1);
});
debugger;
let reactiveEffect2 = effect(reactiveEffect);
console.log(reactiveEffect === reactiveEffect2); // false
1
2
3
4
5
6
7
2
3
4
5
6
7
案例2:
const state = reactive({name:'zf',age:11})
effect(()=>{
console.log('rerender')
if(state.name === 'zf'){
console.log(state.age);
}
})
state.age = 100;
state.name = 'jw';
state.age = 200;
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
每次属性需要重新收集对应的
effect
五.依赖收集
export function track(target: object, type: TrackOpTypes, key: unknown) {
if (!shouldTrack || activeEffect === undefined) { // 是否要收集依赖
return
}
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
if (!dep.has(activeEffect)) { // 维护依赖收集结构
dep.add(activeEffect)
activeEffect.deps.push(dep) // 将dep 推入到deps中
if (__DEV__ && activeEffect.options.onTrack) {
activeEffect.options.onTrack({
effect: activeEffect,
target,
type,
key
})
}
}
}
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
六.触发更新
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
const depsMap = targetMap.get(target)
if (!depsMap) {
// never been tracked
return
}
const effects = new Set<ReactiveEffect>()
const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
if (effectsToAdd) { // 用于去重
effectsToAdd.forEach(effect => {
if (effect !== activeEffect || effect.allowRecurse) {
effects.add(effect)
}
})
}
}
if (type === TriggerOpTypes.CLEAR) { // 是清空,则调用所有effect
// collection being cleared
// trigger all effects for target
depsMap.forEach(add)
} else if (key === 'length' && isArray(target)) {
depsMap.forEach((dep, key) => { // 数组更新
if (key === 'length' || key >= (newValue as number)) {
add(dep)
}
})
} else {
// schedule runs for SET | ADD | DELETE
if (key !== void 0) { // 针对key来进行操作
add(depsMap.get(key))
}
// also run for iteration key on ADD | DELETE | Map.SET
switch (type) {
case TriggerOpTypes.ADD:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
if (isMap(target)) { // 针对map的情况
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
} else if (isIntegerKey(key)) { // 如果增加了某一项 更新数组
// new index added to array -> length changes
add(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE: // 删除处理
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
case TriggerOpTypes.SET: // 针对map的修改处理
if (isMap(target)) {
add(depsMap.get(ITERATE_KEY))
}
break
}
}
const run = (effect: ReactiveEffect) => {
if (__DEV__ && effect.options.onTrigger) {
effect.options.onTrigger({
effect,
target,
key,
type,
newValue,
oldValue,
oldTarget
})
}
if (effect.options.scheduler) { // 有scheduler选项,会调用scheduler
effect.options.scheduler(effect)
} else {
effect() // 否则直接执行effect
}
}
effects.forEach(run)
}
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
81
82
83
84
85
86
87
88
89
90
91
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
81
82
83
84
85
86
87
88
89
90
91
案例1:
const state = reactive({name:'zf',age:11});
effect(()=>{
debugger
state.name;
});
debugger;
state.name = 'zs'
1
2
3
4
5
6
7
2
3
4
5
6
7
调试track和effect函数
案例2:
const state = reactive({ name: 'zf', age: 11 });
effect(()=>{ state.name },{
scheduler:(effect)=>{
console.log('更新')
}
})
state.name = 'jw'
1
2
3
4
5
6
7
2
3
4
5
6
7
effect支持传入scheduler参数