Vue3中,我们如果使用组合式API,那么就需要我们自己通过reactive模块,告诉Vue,哪些数据是需要变为响应式。

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
<template>
<div>
<p>{{ state.msg }}</p>
<button @click="random">Random msg</button>
</div>
</template>

<script>
import { reactive,ref } from 'vue'
export default {
setup() {
const state = reactive({
msg: 'msg reactive'
})
const random = function() {
state.msg = Math.random()
}
return {
random,
state
}
}
}
</script>

来看下reactive的具体实现。首先判断如果是一个readonly对象,就直接返回,因为readonly对象不可以被set,也就不需要去创建响应式对象了。然后通过createReactiveObject这个方法去创建一个响应式对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// packages/reactivity/src/reactive.ts
export function reactive(target: object) {
// target存在并且是一个readonly的,直接返回这个target
// if trying to observe a readonly proxy, return the readonly version.
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
// 创建响应式对象
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
// 是一个WeakMap对象
reactiveMap // export const reactiveMap = new WeakMap<Target, any>()
)
}

createReactiveObject

这个方法就是用来创建响应式对象,首先判断如果target不是一个对象,那么会报一个警告并且返回,target必须是一个对象或者数组。
然后判断如果已经被代理过,那么也直接返回当前target
然后如果当前的代理集合中已经存在能获取到,也直接返回当前被代理的对象
最后做了一个检测,如果是带有 __v_skip 属性的对象、被冻结的对象,或者不在白名单的对象,比如Date类型对象等不可以变成响应式对象, Vue3 仅会对 Array、Object、Map、Set、WeakMap、WeakSet 生成代理,其他对象会被标记为 INVALID
最后通过Proxytarget劫持,将其变成响应式对象。这里有一个判断,通过targetType来决定是使用collectionHandlers还是使用baseHandlers
最后设置到proxyMap中,proxyMap存储生成代理对象后的Map对象,再返回创建好的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
39
40
41
42
43
44
45
// packages/reactivity/src/reactive.ts
function createReactiveObject(
target,
isReadonly, // readonly这个api会传一个true,reactive会传false
baseHandlers,
collectionHandlers,
proxyMap // export const reactiveMap = new WeakMap<Target, any>()
) {
// 如果target是一个对象
if (!isObject(target)) {
{
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
// 如果已经被代理,那么直接返回。
// 如果是 readonly 作用于一个响应式对象,则继续
if (target["__v_raw" /* RAW */] &&
!(isReadonly && target["__v_isReactive" /* IS_REACTIVE */])) {
return target;
}
// target already has corresponding Proxy
// 目标已经有对应的Proxy,proxyMap是一个weakmap
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 // 0) {
return target
}
/**
* 'Map' 'Set' 'WeakMap' WeakSet' 如果是这几类就是TargetType.COLLECTION
*/
/**
* 'Object' 'Array'这两类是 TargetType.COMMON
*/
const proxy = new Proxy(target, targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers);
proxyMap.set(target, proxy);
return proxy;
}

new Proxy

然后通过new proxy创建一个代理对象返回

1
2
3
4
const proxy = new Proxy(target, targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers);
proxyMap.set(target, proxy);
return proxy;
}

这里的targetTyp是上面判断出来的数据类型,如果是2,那么就是Map,Set,WeakMap,WeakSet这几个类型,用collectionHandlers,当target 类型是 ArrayObject 时,就是baseHandlers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// packages/reactivity/src/reactive.ts
function targetTypeMap(rawType: string) {
switch (rawType) {
case 'Object':
case 'Array':
return TargetType.COMMON // 1
case 'Map':
case 'Set':
case 'WeakMap':
case 'WeakSet':
return TargetType.COLLECTION // 2
default:
return TargetType.INVALID // 0
}
}

简单来讲,mutableHandlers就是会劫持我们的对象操作。

  • 访问对象属性会触发 get 函数;
  • 设置对象属性会触发 set 函数;
  • 删除对象属性会触发 deleteProperty 函数;
  • in 操作符会触发has函数;
  • 通过 Object.getOwnPropertyNames 访问对象属性名会触发 ownKeys 函数。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //packages/reactivity/src/baseHandlers.ts
    /**
    * 劫持对对象的操作,访问会触发get,设置会触发set,删除处罚delete,in触发has
    * Object.getOwnPropertyNames 触发 ownKeys 函数
    */
    export const mutableHandlers: ProxyHandler<object> = {
    get, // createGetter()
    set, // createSetter()
    deleteProperty,
    has,
    ownKeys
    }
    当我们触发上面的这些不管哪个,都会做依赖搜集和派发更新这两个操作中的一个。

    依赖收集:createGetter | get 函数

    get函数通过createGetter创建。
    这个函数首先对key做了判断, 如果 get 访问的 key__v_isReactive,返回 createGetter isReadonly 参数取反结果,如果 get 访问的 key__v_isReadonly,返回 createGetterisReadonly 参数,如果 get 访问的 key__v_raw,并且 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
    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
    92
    93
    94
    95
    96
    97
    98
    99
    // packages/reactivity/src/baseHandlers.ts
    const get = /*#__PURE__*/ createGetter()

    // 依赖搜集get函数
    // 依赖搜集发生在数据访问阶段,被访问时候触发get函数
    /**
    * isReadonly:只读
    * shallow: 是否是一个对象
    *
    */
    /**
    *
    * target如果是数组,比如a = reactive([1]);a.push(2)时候,此时的key就是push,然后会再回到arrayInstrumentations方法中再次触发get,此时key是length
    */
    function createGetter(isReadonly = false, shallow = false) {
    return function get(target, key, receiver) {
    // 对特殊的key做处理
    /**
    * key是length表示数组长度发生了变化
    */
    if (key === ReactiveFlags.IS_REACTIVE) {// "__v_isReactive"
    // 代理 observed.__v_isReactive,判断是不是reactive 对象
    return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {// "__v_isReadonly"
    // 代理 observed.__v_isReadonly,判断是不是readonly 对象
    return isReadonly
    } else if (
    // 代理 observed.__v_raw
    key === ReactiveFlags.RAW && // "__v_raw"
    receiver ===
    (isReadonly
    ? shallow
    ? shallowReadonlyMap
    : readonlyMap
    : shallow
    ? shallowReactiveMap
    : reactiveMap
    ).get(target)
    ) {
    return target
    }

    const targetIsArray = isArray(target) // 仅限于reactive([1])这种数组情况
    // 不是只读 是数组,且 key 命中了 arrayInstrumentations
    // 如果target是一个数组,当我们去访问'push', 'pop', 'shift', 'unshift', 'splice','includes', 'indexOf', 'lastIndexOf'会触发依赖搜集
    /**
    * arrayInstrumentations = {
    * includes: ƒ (...args)
    indexOf: ƒ (...args)
    lastIndexOf: ƒ (...args)
    pop: ƒ (...args)
    push: ƒ (...args)
    shift: ƒ (...args)
    splice: ƒ (...args)
    unshift: ƒ (...args)
    * }
    */
    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
    // 获取到对应的方法返回,比如.push时候执行的就会是被代理过的方法
    return Reflect.get(arrayInstrumentations, key, receiver)
    }
    // 获取当前值
    const res = Reflect.get(target, key, receiver)
    // 内置元素symbol 不需要依赖搜集
    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
    return res
    }
    // 如果不是只读,依赖搜集
    // 只读属性不会被修改,所以不用跟踪变化
    if (!isReadonly) {
    track(target, TrackOpTypes.GET, key)
    }
    // 只对最外层响应式,不执行嵌套对象的响应式转换
    /**
    * o:{
    * a:{
    * b: {}
    * }
    * }
    */
    if (shallow) {
    return res
    }
    // __v_isRef
    if (isRef(res)) {
    // ref unwrapping - does not apply for Array + integer key.
    const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
    return shouldUnwrap ? res.value : res
    }
    // 对拿到的结果,执行嵌套对象的响应式转换
    // 如果子值依然是对象,递归处理为响应式对象
    // 这里和Object.defineProperty API一样,不能劫持子对象的变化,但是Object.defineProperty API是在开始就遍历了data中的数据, Proxy是在对象被访问时候才去递归执行下一步 reactive,性能上会有提升,这里获取到值是一个对象的话就去遍历转换
    if (isObject(res)) {
    return isReadonly ? readonly(res) : reactive(res) // 递归reactive
    }

    return res
    }
    }
    在后面就是对当前target是数组情况做了判断,如果是一个数组,并且不是只读,arrayInstrumentations代理了includes ,indexOf,lastIndexOf,pop,push,shift,splice,unshift几个方法,执行数组这个几个方法会走到被代理过的方法里面,有点类似Vue2中的数组劫持。这里为什么是这几个方法呢?
  1. 因为一旦数组的元素被修改,数组的这几个 API 的返回结果都可能发生变化,所以我们需要跟踪数组每个元素的变化(includes、indexOf、lastIndexOf)

  2. 会改变自身长度的数组方法,需要避免 length 被依赖收集,因为这样可能会造成循环引用(push、pop、shift、unshift、splice)

    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

    ;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
    const method = Array.prototype[key] as any
    arrayInstrumentations[key] = function(this: unknown[], ...args: unknown[]) {
    // 把数据转换成原始数据
    const arr = toRaw(this)
    for (let i = 0, l = this.length; i < l; i++) {
    // 依赖搜集
    track(arr, TrackOpTypes.GET, i + '')
    }
    // we run the method using the original args first (which may be reactive)
    // 我们首先使用原始参数运行该方法(可能是反应性的)
    const res = method.apply(arr, args)
    if (res === -1 || res === false) {
    // if that didn't work, run it again using raw values.
    return method.apply(arr, args.map(toRaw))
    } else {
    return res
    }
    }
    })

    ;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
    const method = Array.prototype[key] as any
    arrayInstrumentations[key] = function(this: unknown[], ...args: unknown[]) {
    pauseTracking()
    const res = method.apply(this, args)
    resetTracking()
    return res
    }
    })

    然后通过Reflect求值,执行track函数搜集依赖。

    1
    2
    3
    4
    5
    // 如果不是只读,依赖搜集
    // 只读属性不会被修改,所以不用跟踪变化
    if (!isReadonly) {
    track(target, TrackOpTypes.GET, key)
    }

    track

    首先判断activeEffect当前的副作用函数不存在或者shouldTrack是否可以被依赖搜集的标志位不存在,那就直接返回。
    这里我们比如写在模板中时候{{state}},那么在编译环节,生成的render函数就会访问这个对象,从而触发依赖搜集

    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
    /**
    * 模板中写的{{state}} 编译成渲染函数会访问这里,触发依赖搜集,模板中写的才会触发这里
    * const state = Vue.reactive({
    msg: 'msg reactive',
    arr:[],
    deepObj:{
    test:{a:1}
    },
    deepObj2:{
    test:{a:1}
    }
    })
    模板中访问{{ state.msg }}
    此时的target,type,key分别为
    第一次 target: {"msg":"msg reactive","arr":[],"deepObj":{"test":{"a":1}},"deepObj2":{"test":{"a":1}}},type:get,key:msg

    模板中访问{{state.deepObj.test}},里面的每一个对象都会被触发一次
    触发 track -> target: {"msg":"msg reactive","arr":[],"deepObj":{"test":{"a":1}},"deepObj2":{"test":{"a":1}}} type:get key deepObj
    触发 track -> target: {"test":{"a":1}} type:get key test
    触发 track -> target: {"a":1} type:get key toJSON
    触发 track -> target: {"a":1} type:iterate key Symbol(iterate)
    触发 track -> target: {"a":1} type:get key a
    */
    export function track(target: object, type: TrackOpTypes, key: unknown) {
    /**
    * shouldTrack 是否应该搜集依赖,我们push方法或者别的执行,会把shouldTrack设置为true
    * activeEffect 当前激活的 effect
    */
    // 比如我们只写了一个console.log(state.num),这里到下面就会结束,不会再触发依赖搜集
    if (!shouldTrack || activeEffect === undefined) {
    return
    }
    // const targetMap = new WeakMap<any, KeyToDepMap>()
    let depsMap = targetMap.get(target)
    if (!depsMap) {
    // 没有被搜集过,每一个target都对应一个Map
    targetMap.set(target, (depsMap = new Map()))
    }
    let dep = depsMap.get(key)
    if (!dep) {
    // 每个 key 对应一个 dep 集合
    depsMap.set(key, (dep = new Set()))
    }
    // 依赖搜集就是搜集数据变化后执行的副作用函数
    /**
    * target:{
    * key1:[effect1,effect2],
    * key2:[effect1,effect2]
    * }
    * 比如number.a
    * number(target):{
    * a(key):[effect1,effect2](depsMap)
    * }
    */
    if (!dep.has(activeEffect)) { // activeEffect 当前激活的副作用函数,数据变化后执行的副作用函数。
    // 搜集当前的effect函数 作为依赖
    dep.add(activeEffect)
    // 当前激活的 effect 收集 dep 集合作为依赖
    activeEffect.deps.push(dep)
    if (__DEV__ && activeEffect.options.onTrack) {
    activeEffect.options.onTrack({
    effect: activeEffect,
    target,
    type,
    key
    })
    }
    }
    }

    然后保存一个这种格式。每次track,把当前激活的activeEffect作为依赖,搜集到target相关的depsMap对应key下的依赖集合dep

    1
    2
    3
    4
    5
    targetMap: WeakMap{ 
    target:Map{
    key: Set[effect1,effect2...]
    }
    }

    派发更新:createSetter | set函数

    当我们去修改值的时候,会触发派发通知,因为被proxy代理,所以会执行set函数。
    set函数主要逻辑是通过Reflect.set求值,然后通过trigger函数派发通知。

    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
    // packages/reactivity/src/baseHandlers.ts
    const set = /*#__PURE__*/ createSetter()
    // 派发通知发生在数据更新的阶段
    // 响应式对象属性更新会触发set函数
    function createSetter(shallow = false) {
    return function set(
    target, //数组的话就是我们的那个数组
    key, // 如果是数组push,key就是当前数组长度length字符串,比如 '3',pop这里的key是length,splice的key是'0'
    value, // 设置的新值
    receiver
    ): boolean {
    // 获取到当前的值
    let oldValue = (target as any)[key]
    // 如果不是浅代理,证明当前对象里面所有的都是响应式的深层代理
    if (!shallow) {
    // 这里需要toRaw原因是传入的有可能是响应式对象
    value = toRaw(value) // 将新的值,设置的响应式值转换为普通值
    oldValue = toRaw(oldValue) //将旧的值,设置的响应式值转换为普通值
    if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
    // 目标对象不是数组,旧值是ref(已经被reactive包装过),新值不是ref,直接赋值
    oldValue.value = value
    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) // 判断target和key,判断是新增还是修改值
    // 设置值
    const result = Reflect.set(target, key, value, receiver)
    // don't trigger if target is something up in the prototype chain of original
    // 如果目标的原型链也是一个 proxy,通过 Reflect.set 修改原型链上的属性会再次触发 setter,这种情况下就没必要触发两次 trigger 了
    if (target === toRaw(receiver)) {
    // trigger 用来做派发通知
    if (!hadKey) { // 新增值
    trigger(target, TriggerOpTypes.ADD, key, value)
    } else if (hasChanged(value, oldValue)) { // 修改值
    trigger(target, TriggerOpTypes.SET, key, value, oldValue)
    }
    }
    return result
    }
    }

    首先判断如果老的值已经是一个Ref响应式对象,那么我们直接赋值即可,因为老的值已经是被ref,我们重新赋值,就会触发set函数,所以这里直接return,不需要去trigger

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 获取到当前的值
    let oldValue = (target as any)[key]
    // 如果不是浅代理,证明当前对象里面所有的都是响应式的深层代理
    if (!shallow) {
    // 这里需要toRaw原因是传入的有可能是响应式对象
    value = toRaw(value) // 将新的值,设置的响应式值转换为普通值
    oldValue = toRaw(oldValue) //将旧的值,设置的响应式值转换为普通值
    if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
    // 目标对象不是数组,旧值是ref(已经被reactive包装过),新值不是ref,直接赋值
    // 这里为什么不需要去trigger呢?因为老的值已经是被ref,我们重新赋值,就会触发
    oldValue.value = value
    return true
    }
    } else {
    // in shallow mode, objects are set as-is regardless of reactive or not
    }

    然后判断当前我们对这个值是新增还是修改,然后触发trigger函数派发通知

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    const hadKey =
    isArray(target) && isIntegerKey(key)
    ? Number(key) < target.length // 数组的话,判断新的长度和旧的长度
    : hasOwn(target, key) // 判断target和key,判断是新增还是修改值
    // 设置值
    const result = Reflect.set(target, key, value, receiver)
    // 如果目标的原型链也是一个 proxy,通过 Reflect.set 修改原型链上的属性会再次触发 setter,这种情况下就没必要触发两次 trigger 了
    if (target === toRaw(receiver)) {
    // trigger 用来做派发通知
    if (!hadKey) { // 新增值
    trigger(target, TriggerOpTypes.ADD, key, value)
    } else if (hasChanged(value, oldValue)) { // 修改值
    trigger(target, TriggerOpTypes.SET, key, value, oldValue)
    }
    }
    return result

    trigger

    trigger函数主要做以下几个事情

  3. 首先获取从targetMap获取depsMap集合,这个集合已经在track时候依赖搜集起来。

  4. 创建effects集合

  5. 根据keydepsMap从获取当前的effect集合并且通过add函数添加

  6. 遍历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
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
92
93
94
95
96
97
98
99
100
const targetMap = new WeakMap<any, KeyToDepMap>()
// packages/reactivity/src/effect.ts
export function trigger(
target,
type, // 判断是新增修改还是删除
key,
newValue,
oldValue, // 修改会有这个参数,老的value
oldTarget
) {
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) { // clear类型,触发这个对象所有的effect
// collection being cleared 收集被清除
// trigger all effects for target 触发target的所有effects
depsMap.forEach(add)
} else if (key === 'length' && isArray(target)) {
depsMap.forEach((dep, key) => {
// 为什么要判断key是length的情况?这里是做了优化,因为proxy操作数组,会触发多次!
// 所以只有在set length的时候才会去调用add方法,然后统一执行所有的更新
if (key === 'length' || key >= (newValue as number)) {
add(dep)
}
})
} else {
// void 0 为什么不直接写undefined?因为undefined可能会被赋值修改
if (key !== void 0) {
add(depsMap.get(key))
}

// also run for iteration key on ADD | DELETE | Map.SET
switch (type) {
case TriggerOpTypes.ADD: // 新增,比如push
if (!isArray(target)) { // 不是数组
add(depsMap.get(ITERATE_KEY)) // 触发 ITERATE_KEY 的 effect
if (isMap(target)) { // Map数据结构
add(depsMap.get(MAP_KEY_ITERATE_KEY)) // 触发 MAP_KEY_ITERATE_KEY 的 effect
}
} else if (isIntegerKey(key)) {
// new index added to array -> length changes
// 添加到数组的新索引 -> 长度更改
add(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE: // 一般用pop,或者delete xxx一个值,type会是delete
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
case TriggerOpTypes.SET: // 修改一个对象值,splice(0,1,3)都是set
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
})
}
// 计算属性会调度执行,把this._dirty设置为true,然后执行trigger派发通知,运行该计算属性的 activeEffect
if (effect.options.scheduler) { // 如果传入了调度函数,则通过scheduler去运行effect
// 调度执行
// scheduler 可以让用户自己选择调用的时机
// 这样就可以灵活的控制调用了
effect.options.scheduler(effect)
} else {
// 直接执行,执行的是reactiveEffect
effect()
}
}

effects.forEach(run)
}

readonly

Vue中,我们可以通过readonly来实现把一个对象变为只读,因为如果我们使用const的话,还是可以修改对象中的属性的。

1
2
3
4
const obj = Vue.readonly({a:1})
obj.b = 1
// 不能赋值,会有warning警告
// Set operation on key "b" failed: target is readonly.

看下里面的具体实现。也是基于createReactiveObject实现的

createReactiveObject

1
2
3
4
5
6
7
8
9
10
11
12
13
// packages/reactivity/src/reactive.ts
export function readonly<T extends object>(
target: T
): DeepReadonly<UnwrapNestedRefs<T>> {
return createReactiveObject(
target,
true,
//这里传入的是readonlyHandlers,readonlyCollectionHandlers
readonlyHandlers,
readonlyCollectionHandlers,
readonlyMap
)
}

reactive中的有区别的就是baseHandlers,collectionHandlers的传入,因为readonly不允许对象的修改和删除。这里的baseHandlersreadonlyHandlers,collectionHandlersreadonlyCollectionHandlers

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
// packages/reactivity/src/reactive.ts
function createReactiveObject(
target,
isReadonly, // readonly这个api会传一个true,reactive会传false
baseHandlers,
collectionHandlers,
proxyMap // export const reactiveMap = new WeakMap<Target, any>()
) {
// 如果target是一个对象
if (!isObject(target)) {
{
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
// 如果已经被代理,那么直接返回。
// 如果是 readonly 作用于一个响应式对象,则继续
if (target["__v_raw" /* RAW */] &&
!(isReadonly && target["__v_isReactive" /* IS_REACTIVE */])) {
return target;
}
// target already has corresponding Proxy
// 目标已经有对应的Proxy,proxyMap是一个weakmap
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 // 0) {
return target
}
/**
* 'Map' 'Set' 'WeakMap' WeakSet' 如果是这几类就是TargetType.COLLECTION
*/
/**
* 'Object' 'Array'这两类是 TargetType.COMMON
*/
const proxy = new Proxy(target, targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers);
proxyMap.set(target, proxy);
return proxy;
}

来看下createReactiveObjectreadonlyCollectionHandlers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// packages/reactivity/src/baseHandlers.ts
// 只读属性
// 禁止设置和删除
export const readonlyHandlers: ProxyHandler<object> = {
get: readonlyGet,
set(target, key) {
if (__DEV__) {
console.warn(
`Set operation on key "${String(key)}" failed: target is readonly.`,
target
)
}
return true
},
deleteProperty(target, key) {
if (__DEV__) {
console.warn(
`Delete operation on key "${String(key)}" failed: target is readonly.`,
target
)
}
return true
}
}

当我们对其进行setdelete时候,会报一个警告,禁止我们去设置和删除值。
这里的getreadonlyGet,createGetter方法创建的

1
const readonlyGet = /*#__PURE__*/ createGetter(true) // 只读属性的get
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// packages/reactivity/src/baseHandlers.ts    
// 如果不是只读,依赖搜集
// 只读属性不会被修改,所以不用跟踪变化
// createGetter
function createGetter(isReadonly = false,shallow = false) {
return function get(target, key, receiver) {
// ...
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
//...
}
}

这里和reactive中的createGetter最大的区别是,不需要再去track执行依赖搜集,因为是只读的,并不会去修改他,也就不需要跟踪变化了。

ref

Vue中,可以通过ref将一个基础数据类型转换为一个响应式对象。因为reactive API会有一个判读,必须是Object或者Array

1
2
const num = ref(10)
num.value = 100

这里是通过createRef创建返回的。

createRef

1
2
3
4
// packages/reactivity/src/ref.ts
export function ref(value) {
return createRef(value)
}

首先通过isRef对当前的值做了判断,如果已经是一个响应式对象,那么就返回这个响应式对象。否则就去通过new RefImp实例化

1
2
3
4
5
6
function createRef(rawValue, shallow = false) {
if (isRef(rawValue)) { // 如果已经是一个响应式ref,直接返回这个即可
return rawValue
}
return new RefImpl(rawValue, shallow)
}

new RefImpl

当我们去实例化RefImpl时候,会先调用constructor中的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class RefImpl<T> {
private _value: T

public readonly __v_isRef = true

constructor(private _rawValue: T, public readonly _shallow = false) {
this._value = _shallow ? _rawValue : convert(_rawValue) // 如果当前是一个对象,就用reactive
}
// 访问count.value就会触发这里
get value() {
// 依赖搜集,key为固定value
track(toRaw(this), TrackOpTypes.GET/** get */, 'value')
return this._value
}
// count.value+1触发set
set value(newVal) {
if (hasChanged(toRaw(newVal), this._rawValue)) {
this._rawValue = newVal
this._value = this._shallow ? newVal : convert(newVal)
// 派发通知
trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
}
}
}

首先通过_shallow做了一次判断,决定采用传入的原始值还是调用convert处理。
convert很简单,就是判读当前的值如果是数组或者对象,那么就用reactive,否则就返回当前的val

1
2
3
4
//packages/reactivity/src/ref.ts
// 如果当前是一个对象,就用reactive
const convert = <T extends unknown>(val: T): T =>
isObject(val) ? reactive(val) : val

再然后,就是定义了getset函数。当我们访问数据会触发get,从而触发track函数进行依赖搜集。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 访问count.value就会触发这里
get value() {
// 依赖搜集,key为固定value
track(toRaw(this), TrackOpTypes.GET/** get */, 'value')
return this._value
}
// count.value+1触发set
set value(newVal) {
if (hasChanged(toRaw(newVal), this._rawValue)) {
this._rawValue = newVal
this._value = this._shallow ? newVal : convert(newVal)
// 派发通知
trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
}
}

当我们设置值时候会触发set函数,这里首先通过hasChanged判断一个值是否有更改,通过判断了NaN的情况。

1
2
3
4
// packages/shared/src/index.ts
// compare whether a value has changed, accounting for NaN.
export const hasChanged = (value: any, oldValue: any): boolean =>
value !== oldValue && (value === value || oldValue === oldValue)

如果新的值有更新,就会通过trigger来去派发通知。

1
2
3
4
5
6
7
8
set value(newVal) {
if (hasChanged(toRaw(newVal), this._rawValue)) {
this._rawValue = newVal
this._value = this._shallow ? newVal : convert(newVal)
// 派发通知
trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
}
}

副作用函数

triggertrack中我们可以看到,都离不开effect。那么我们来看下effect相关的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 全局 effect 栈
const effectStack: ReactiveEffect[] = []
// 当前激活的副作用函数 effect
let activeEffect: ReactiveEffect | undefined

// 响应式函数
// 创建一个 wrapper,它是一个响应式的副作用的函数
export function effect<T = any>(
fn: () => T,
options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
if (isEffect(fn)) { // 如果传入的fn已经是一个副作用函数了,则指向原始函数
fn = fn.raw
}
const effect = createReactiveEffect(fn, options)
if (!options.lazy) { // 和vue2一样,lazy也是代表计算属性和侦听器,非lazy计算属性则直接执行一次
effect()
}
return effect
}

这个函数的主要作用是通过createReactiveEffect去创建一个叫做reactiveEffect()的副作用函数,将当前的副作用函数压入栈,并且将effect赋值给全局的activeEffect,这里在依赖搜集时候会把当前的activeEffect搜集起来, 然后执行原始函数。

createReactiveEffect

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
// packages/reactivity/src/effect.ts
let uid = 0
// 为当前函数创建副作用函数,计算属性侦听器也会执行这里
function createReactiveEffect<T = any>(
fn: () => T,
options: ReactiveEffectOptions
): ReactiveEffect<T> {
// 创建reactiveEffect副作用函数
const effect = function reactiveEffect(): unknown {
if (!effect.active) { // 非激活状态,则判断如果非调度执行,直接执行原始函数
return options.scheduler ? undefined : fn() // computed和watch都有调度执行
}
if (!effectStack.includes(effect)) {
// 清空effect引用的依赖
cleanup(effect)
try {
// 开启全局 shouldTrack ,允许依赖搜集
enableTracking()
// 压栈
effectStack.push(effect)
activeEffect = effect
// 执行原始函数
return fn()
} finally {
// 出栈
effectStack.pop()
// 恢复shouldTrack开启之前的状态
resetTracking()
// 指向栈最后一个effect
activeEffect = effectStack[effectStack.length - 1]
}
}
} as ReactiveEffect
effect.id = uid++
effect.allowRecurse = !!options.allowRecurse
// 标识是一个effect函数
effect._isEffect = true
// effect自身状态
effect.active = true
// 包装的原始函数
effect.raw = fn
// effect对应的依赖,双向指针,依赖包含对effect的引用,effect也包含对依赖的引用
effect.deps = []
// effect的相关配置
effect.options = options
return effect
}