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
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

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

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

对象不会被重复代理,同时如果已经是代理对象也不会再次进行代理

案例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

通过代理对象可以获取被代理数据,必要时可以使用markRaw标记对象,可以避免对象的代理操作

二.baseHandlers实现

export const mutableHandlers: ProxyHandler<object> = {
  get, // 访问属性代理
  set, // 设置对象属性代理
  deleteProperty, // 删除属性代理
  has, // 访问in操作触发
  ownKeys // 调用Object.getOwnPropertyNames()方法时触发
}
1
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

案例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).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

案例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

如果设置的值是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

原型链也是一个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
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
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

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).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

针对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

针对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
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

案例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:

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

每次属性需要重新收集对应的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

六.触发更新

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

案例1:

const state = reactive({name:'zf',age:11});
effect(()=>{
    debugger
    state.name;
});
debugger;
state.name = 'zs'
1
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

effect支持传入scheduler参数