前言
自从很久之前(已经忘了多久了)Vue发布了alpha版本以来,网上已经涌现了一大批优秀的博客、文章来分析Vue3的全新的响应式系统,所以我在这个时候再写一篇分析关于Vue响应式系统的文章可以说是在炒冷饭的感觉了。不过我还是想把自己看源码时的想法记录分享出来供大家的批评指正(顺便在掘金混个脸熟,提升提升等级,😁😁😁)。闲话不多说,我们直接开始吧。
正文
简单说一下怎么调试吧
Vue3的源码已经为我们搭建好了非常完美的在vscode中的调试环境,我们从Vue3仓库中将源码clone下来(推荐先fork到自己的仓库中,再clone自己仓库下面的源码),然后使用vscode打开源码目录。vue3响应式系统中的源码在packages/reactivity
目录下,因此这片文章中我们主要分析packages/reactivity
下面的源码。
packages/reativity
目录文件如下:src
目录下存放着响应式系统真正的代码,__tests__
对应着单元测试。
我们选择__test__
目录下effect.spec.ts
来调试响应式系统的源码。我们打开effect.spec.ts
文件,然后在对象的地方搭上断点,打开vscode
页面,点击调试按钮,就可以开始调试了(或者按f5
直接开始调试)
正式阅读源码
我们就就用上一个图中的单元测试作为例子开始一步步的阅读Vue3.0当核心的reactive
和effect
的源码。阅读之前请先保证您熟悉ES6的相关知识,尤其是Proxy相关的内容,不熟悉的朋友可以先阅读一下阮一峰老师的ES6入门教程
let dummy
// 创建reactive对象
const counter = reactive({ num: 0 })
const fn = () => {
dummy = counter.num
}
effect(fn)
expect(dummy).toBe(0)
counter.num = 7
expect(dummy).toBe(7)
reactive
单元测试中首先调用reactive
函数创建一个reactive proxy(响应式proxy
,后文中响应式proxy
和响应式对象
是同一个意思), reactive
函数定义在src/reactive.ts
文件中,reactive
的定义实现如下,它接受一个object
,然后返回一个reactive proxy。
export interface Target {
[ReactiveFlags.SKIP]?: boolean // 是否跳过reative
[ReactiveFlags.IS_REACTIVE]?: boolean // 是否是reactive proxy
[ReactiveFlags.IS_READONLY]?: boolean // 是否是readonly proxy
[ReactiveFlags.RAW]?: any // reactive proxy的原始数据
}
...
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.
// 如果当前target已经是一个readonly proxy了,则直接返回
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
// 调用createReactiveObject创建reactive对象
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers
)
}
reactive
首先判断target是不是一个readonly proxy,如果是则直接返回。
在这里我先说一点后面分析时会说到的内容, 调用reactive
函数创建的的reactive proxy中会定义一个ReactiveFlags.IS_REACTIVE
属性,该属性为true
时表明该proxy是一个reactive proxy,而readonly
返回的readonly proxy会定义一个ReactiveFlags.IS_READONLY
属性来表明该proxy
是一个reactive proxy
,所以reactive
函数开始会先判断target
是否是一个readonly proxy
,如果是,则直接返回,防止修改到只读proxy
中的数据
// 如果当前target已经是一个readonly proxy了,则直接返回
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
接着reactive
调用createReactiveObject
函数来创建reactive
对象。
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers
)
reactive
给createReactObject
传递了四个参数,这四个参数代表意义我们可以从createReactiveObject
的定义可以看出,target
是我们传给reactive
的参数,是个普通object或者是集合(Map,Set, WeekMap,WeekSet)
。isReadonly
在当我们使用readonly
函数创建readonly proxy
时为true
,表示创建一个readonly proxy
,baseHandlers
和collectionHandlers
都是使用new Proxy
时传递给Proxy
构造函数的第二个参数。
/**
*
* @param target 传入的object,或者集合(map、set之类)
* @param isReadonly 是否是readonly
* @param baseHandlers target是普通object时 传给Proxy构造函数的第二个参数
* @param collectionHandlers target是集合(map、set等)时 传给Proxy构造函数的第二个参数
*/
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>
) {
...
}
我们可以先看一下reactive
传递给createReactiveObject
的baseHandlers
和collectionHandlers
是怎样子的。代码在src/baseHandlers
文件下
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys
}
...
export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {
get: createInstrumentationGetter(false, false)
}
我们可以看到mutableHandlers
定义了get
、set
等一系列的拦截器,而集合类型(Map,Set等)
对应的mutableCollectionHandlers
只定义了get
拦截器,为什么这么做呢,我们不妨来看一个例子。
const map = new Map()
map.set('name', 'tom')
上面中set
的过程调用可以看作
这样子
const set = map.set
set.call(map, 'name', 'tom')
可以看出,在使用set
函数设置新的键值时,会先获取Map
的set
函数,然后再去调用set
去设置键值对。在获取Map
的set
函数会触发Proxy
的get
拦截器。因此,在为集合类型的添加reactive
能力时,我们只需要拦截Proxy的get
操作,这样在Proxy
调用相对应的函数(get,set
等函数)时,返回重写之后的、加入了reactive
逻辑处理的函数, 即可做到对集合类型做响应式处理。
至于handlers
中的get
, set
等一些列拦截器是如何定义的,我们先不去探究,等到掉用到对应的拦截器时在去看对应的代码,现在我们先看看createReactiveObject
是如何定义的
createReactiveObject
createReactObject
的定义如下
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>
) {
// 如何target不是一个object,则直接返回
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已经是一个reactive proxy,则直接返回
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// target already has corresponding Proxy
// 如果target已经有对应的reactive proxy了,则获取对应的proxy并返回
const proxyMap = isReadonly ? readonlyMap : reactiveMap
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// only a whitelist of value types can be observed.
// 判断target是否是可以被reactive,如果不是则直接返回
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// 创建reactive proxy
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
// 将target映射到proxy中,
proxyMap.set(target, proxy)
// 返回
return proxy
}
首先createReactObject
会先判断target
是否是一个对象,如果不是,则直接返回
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
然后在判断target
是否是一个reactive
对象,如果是则直接返回target
。(这里的ReactiveFlags.RAW
,ReactiveFlags.IS_REACTIVE
属性是什么时候定义的在后面讲get
拦截器时会讲到)
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// 这样的情况
/*
const r1 = reactive({})
const r2 = reactive(r1)
r1 === r2 // true
*/
接着,判断target是否有对应的reactive
对象,也就是已经做过reactive
处理了,如果是,获取其对应的reactive
对象并返回
// 如果target已经有对应的reactive proxy了,则获取对应的proxy并返回
const proxyMap = isReadonly ? readonlyMap : reactiveMap
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
readonlyMap
和reactiveMap
是一个WeakMap
,用来存储object
和其对应的reactive proxy
之间的映射关系
export const reactiveMap = new WeakMap<Target, any>()
export const readonlyMap = new WeakMap<Target, any>()
再接着判断target
是否是可以被响应式处理的类型,如果不是,则直接返回。我们可以从getTargetType
定义可以看出,只有特定的一些类型才可以做响应式处理
function targetTypeMap(rawType: string) {
switch (rawType) {
case 'Object':
case 'Array':
return TargetType.COMMON
case 'Map':
case 'Set':
case 'WeakMap':
case 'WeakSet':
return TargetType.COLLECTION
default:
return TargetType.INVALID
}
}
function getTargetType(value: Target) {
// value没有`ReactiveFlags.SKIP`这个属性且没有被‘冻结’的对象、数组、集合才能做响应式处理
return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
? TargetType.INVALID
: targetTypeMap(toRawType(value))
}
做完上面的一系列操作之后,就可以创建响reactive proxy并返回了
// 创建reactive proxy
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
// 存储target和proxy的映射关系,
proxyMap.set(target, proxy)
// 返回
return proxy
到这里,我们已经分析完了reactive
创建reactive proxy
的流程了。接下来我们先来看看effect
函数是如何实现的
const counter = reactive({ num: 0 })
// 分析到这了,接下来分析下面这几行代码
const fn = () => {
dummy = counter.num
}
effect(fn)
effect
effect
函数的定义在src/effect.ts
文件中。effect
函数实现非常简单。如果fn
是一个effect函数,先把fn.raw
复制给fn
,在调用createReactiveEffect
创建reactiveEffect
函数对象。如果options
的lazy
为false,则调用reactiveEffect
函数。最后返回reactiveEffect
函数对象。我们这个例子中options.lazy
是false
,所以创建完成reactiveEffect
函数对象后会直接调用创建好的reactiveEffect
函数对象
export function effect<T = any>(
fn: () => T,
options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
// 如果fn是一个effect函数,则将fn.raw(原始函数)赋值给fn
if (isEffect(fn)) {
fn = fn.raw
}
// 创建effect对象
const effect = createReactiveEffect(fn, options)
// options中lazy为false,即effect不是懒加载的,则调用effect
if (!options.lazy) {
effect()
}
// 返回effect函数
return effect
}
reactiveEffect
函数对用通过createReactiveEffect
函数创建的,接下里我们看看createReactiveEffect
是如何创建reactiveEffect
函数的
createReactvieEffect
createReactEffect
定义如下:
function createReactiveEffect<T = any>(
fn: () => T,
options: ReactiveEffectOptions
): ReactiveEffect<T> {
const effect = function reactiveEffect(): unknown {
if (!effect.active) {
return options.scheduler ? undefined : fn()
}
if (!effectStack.includes(effect)) {
cleanup(effect)
try {
enableTracking()
effectStack.push(effect)
activeEffect = effect
return fn()
} finally {
effectStack.pop()
resetTracking()
activeEffect = effectStack[effectStack.length - 1]
}
}
} as ReactiveEffect
effect.id = uid++
effect.allowRecurse = !!options.allowRecurse
effect._isEffect = true
effect.active = true
effect.raw = fn
effect.deps = []
effect.options = options
return effect
}
createReactiveEffect
函数也非常简单,定义一个reactiveEffect
函数,并为reactiveEffect
设置一系列属性。最后返回reactiveEffect
函数, reactiveEffect
中的deps
属性我们需要特别注意一下,该属性是用来收集effect
所依赖的target
的,就是我们使用reactive
时传递的参数target
的。
前面提到了在我们的例子中,调用createReactiveEffect
创建完reactiveEffect
函数对象后会立即执行reactiveEffect
函数,接下来我们就来看一看reactiveEffect
函数是执行过程是怎样的
reactiveEffect
reactiveEffect
函数定义如下
const effect = function reactiveEffect(): unknown {
// 如果active为false
if (!effect.active) {
// 如果options不存在scheduler则调用fn
return options.scheduler ? undefined : fn()
}
// 如果自己不再effectStack中
if (!effectStack.includes(effect)) {
// 清空自己所依赖的target,并重新收集
cleanup(effect)
try {
// 允许收集依赖
enableTracking()
// 将自己加入当前effectStack
effectStack.push(effect)
// 将自己设置为activeEffect
activeEffect = effect
// 调用fn
return fn()
} finally {
// 将自己出栈
effectStack.pop()
// 重新设置 是否允许收集依赖依赖
resetTracking()
// 重新设置是否activeEffect
activeEffect = effectStack[effectStack.length - 1]
}
}
} as ReactiveEffect
reactiveEffect
函数首先判断自己是否是active
状态,如果不是,则判断options
是否存在scheduler
,如果不存在,则调用我们传入的fn函数并返回。
接下来判断当前的reactiveEffect
(也就是自己)是否在effectStack中,如果不存在,则进入if
语句的代码块
首先,reactiveEffect会清空自己所依赖的target。
cleanup(effect)
// ....
function cleanup(effect: ReactiveEffect) {
const { deps } = effect
if (deps.length) {
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect)
}
deps.length = 0
}
}
这里为什么要清空依赖呢,我们来看下面的例子
const a1 = {name: 'a1'}
const a2 = {name: 'a1'}
const b = {name: 'b', a: a1}
const r = reactive(b)
const e = effect(() => {
console.log(r.name + r.a.name)
})
r.a = a2
从上面的例子来看,一开始e
所依赖的target
有b
和a1
,也就是e
的deps
数组中会在b
和a1
两个依赖,当执行了r.a = a2
之后,e
就不再依赖a1
了,而是依赖a2
,所以如果不清除原来的依赖,当再次收集依赖时,e
的deps
就会有b
、a1
和a2
三个依赖,这明显是不合逻辑的
接着是一段try...finally...
代码,在try代码中reactiveEffect首先调用enableTracking
将shouldTrack
设置为true
(只有shouldTrack
为true
时才允许手机依赖,然后将自己加入到effectStack
中,将activeEffect
设为自己(记住这一点很重要),最后调用fn
,在finally
代码块中将状态还原到之前的状态。
try {
// 允许收集依赖
enableTracking()
// 将自己加入当前effectStack
effectStack.push(effect)
// 将自己设置为activeEffect
activeEffect = effect
// 调用fn
return fn()
} finally {
// 将自己出栈
effectStack.pop()
// 重新设置 是否允许收集依赖依赖
resetTracking()
// 重新设置是否activeEffect
activeEffect = effectStack[effectStack.length - 1]
}
...
export function enableTracking() {
trackStack.push(shouldTrack)
shouldTrack = true
}
export function resetTracking() {
const last = trackStack.pop()
shouldTrack = last === undefined ? true : last
}
try
代码块中最后调用的fn
函数就是我们传递给effect
的参数,fn
通过counter.num
获取num
的值,这时候会出发Proxy
的get
拦截器,也就是前面提到的mutableHandlers
的get
函数(千呼万唤始出来,终于走到了get拦截器),接下来我们来看看get拦截器做了哪些工作
const fn = () => {
dummy = counter.num
}
effect(fn)
get拦截器
get拦截器定义在src/baseHandlers
中,它是通过createGetter
函数定义的
const get = /*#__PURE__*/ createGetter()
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
// 如果key是ReactiveFlags.IS_REACTIVE,ReactiveFlags.IS_READONLY, ReactiveFlags.RAW,则直接返回对应的值
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
} else if (
key === ReactiveFlags.RAW &&
receiver === (isReadonly ? readonlyMap : reactiveMap).get(target)
) {
return target
}
// 判断target是否是一个数组
const targetIsArray = isArray(target)
// 如果taget是数组,且key是arrayInstrumentations中的key,则返回arrayInstrumentations[key]
if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
// 获取原始值
const res = Reflect.get(target, key, receiver)
// 如果key是一些特殊的key,则直接返回结果
if (
isSymbol(key)
? builtInSymbols.has(key as symbol)
: key === `__proto__` || key === `__v_isRef`
) {
return res
}
// 如果不是readonly,则开始收集依赖
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
// 如果是浅响应式则直接返回
if (shallow) {
return res
}
// 如果是ref
if (isRef(res)) {
// ref unwrapping - does not apply for Array + integer key.
// 如果不是target不是数组,或者target是数组但key不是数字,则返回res.value,否则返回res
const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
return shouldUnwrap ? res.value : res
}
// 如果res是一个对象,则对res进行响应式处理
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)
}
// 返回res
return res
}
}
get
首先判断key
是不是ReactiveFlags.IS_REACTIVE
,ReactiveFlags.IS_READONLY
, ReactiveFlags.RAW
中的一个,如果是则返回对应的值。
接着,如果target
是数组
而且key
存在于arrayInstrumentations
中,则返回Reflect.get(arrayInstrumentations, key, receiver)
。arrayInstrumentations
保存着重写之后的数组的的方法
// 重写数组的方法
;(['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
}
}
})
// instrument length-altering mutation methods to avoid length being tracked
// which leads to infinite loops in some cases (#2137)
;(['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
(反射)获取target.key的值、调用track
开始收集依赖,对res除了一系列处理之后并返回
// 获取原始值
const res = Reflect.get(target, key, receiver)
// 如果key是一些特殊的key,则直接返回结果
if (
isSymbol(key)
? builtInSymbols.has(key as symbol)
: key === `__proto__` || key === `__v_isRef`
) {
return res
}
// 如果不是readonly,则开始收集依赖,readonly Proxy的属性是不可更改,自然就不需要收集依赖了
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
// 如果是浅响应式则直接返回
if (shallow) {
return res
}
// 如果是ref
if (isRef(res)) {
// ref unwrapping - does not apply for Array + integer key.
// 如果不是target不是数组,或者target是数组但key不是数字,则返回res.value,否则返回res
const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
return shouldUnwrap ? res.value : res
}
// 如果res是一个对象,则对res进行响应式处理
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res)
}
// 返回res
return res
好了,到现在终于调用track
函数开始收集依赖了,我们就看看track
如何手机依赖的
track
track
函数定义在src/effect.ts
中。track的逻辑比较容易理解,在这里我就不多说了。需要注意的是,这里的depMap和dep中存储的是前面我们说到的reactiveEffect
函数
export function track(target: object, type: TrackOpTypes, key: unknown) {
// 如果shouldTrack为false,或者activeEffect为undefined,则直接返回
if (!shouldTrack || activeEffect === undefined) {
return
}
// 过去target对应despMap
let depsMap = targetMap.get(target)
// 如果不存在depsMap则创建
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
// 获取target的key对应的dep Set
let dep = depsMap.get(key)
// 如果不存在则创建
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
// 如果dep没有activeEffect,即当前的effect没有被收集到target的key的依赖中,则添加当前effect到dep中
if (!dep.has(activeEffect)) {
// 收集effect
dep.add(activeEffect)
// 将dep添加到当前effect的deps中,在cleanup函数中可以直接获取到该dep
activeEffect.deps.push(dep)
// 开发环境下设置
if (__DEV__ && activeEffect.options.onTrack) {
activeEffect.options.onTrack({
effect: activeEffect,
target,
type,
key
})
}
}
}
track
函数执行完成后,就完成了对应的依赖收集,接下来就是触发依赖。当代码执行到count.num = 7
时,会出发Proxy
的set
拦截器(也就是前面提到的mutableHandlers
的set
函数),在set
拦截器中触发依赖,接下来我们就来看看set
做了哪些工作
set函数
set
函数定义是在src/baseHandlers
文件中,通过createSetter
函数来创建
const set = /*#__PURE__*/ createSetter()
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的原始值,value可能是一个reactive proxy
value = toRaw(value)
// 如果target不是数组,切oldValue不是Ref,且value不是Ref,则将value赋值给oldValue.value
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
}
} else {
// in shallow mode, objects are set as-is regardless of reactive or not
}
// 判断key是否存在target中
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
// 通过Reflect设置target的key属性的值为trigget
const result = Reflect.set(target, key, value, receiver)
// don't trigger if target is something up in the prototype chain of original
if (target === toRaw(receiver)) {
// 如果key不再target中,触发add操作
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
// 如果key存在target中且value不等于oldValue,则触发set操作
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
}
set
函数前面就是通过一系列操作设置新的值(如何设置的大家可以看注释就好了),在最后的调用trigger
函数来触发相对应的依赖,接下来我们就来看看trigger的具体操作
trigger
trigger
函数定义在src/effect.ts
文件中。trigger的职责也很简单,就是找到需要触发的effect(reactiveEffect),然后调用。
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
// 获取target的depsMap
const depsMap = targetMap.get(target)
// 如果不存在depsMap,则返回
if (!depsMap) {
// never been tracked
return
}
// 定义effects 存储需要粗发的依赖
const effects = new Set<ReactiveEffect>()
// 定义add函数来将effect添加到effects
const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect || effect.options.allowRecurse) {
// 如果effect不等于当前的effect,添加到effects
effects.add(effect)
}
})
}
}
if (type === TriggerOpTypes.CLEAR) {
// collection being cleared
// trigger all effects for target
// 如果是clear操作,则将depsMap所有的effect添加到effects
depsMap.forEach(add)
} else if (key === 'length' && isArray(target)) {
// 如果target是数组且length改变了,
depsMap.forEach((dep, key) => {
// 如果key是length或者key >= newValue
/**
* key >= newValue等情况是怎么回事:
* arr = [1, 2, 3]
* 当arr.length = 1,newValue = 1
* 而key有如下值 length, 0(下标), 1(下标), 2(下标),
* 当arr.length = 1,arr[1] arr[2]的值就被删掉了,需要触发对应的依赖
*/
if (key === 'length' || key >= (newValue as number)) {
add(dep)
}
})
} else {
// schedule runs for SET | ADD | DELETE
// 处理普通object的set,add和delete操作
if (key !== void 0) {
add(depsMap.get(key))
}
// also run for iteration key on ADD | DELETE | Map.SET
// 处理Array,Map,Set特殊类型的add,delete,set操作
switch (type) {
case TriggerOpTypes.ADD:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
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:
if (isMap(target)) {
add(depsMap.get(ITERATE_KEY))
}
break
}
}
// 调用reactiveEffect函数
const run = (effect: ReactiveEffect) => {
if (__DEV__ && effect.options.onTrigger) {
effect.options.onTrigger({
effect,
target,
key,
type,
newValue,
oldValue,
oldTarget
})
}
if (effect.options.scheduler) {
effect.options.scheduler(effect)
} else {
effect()
}
}
effects.forEach(run)
}
结尾
到这里,其实Vue3响应式系统核心的整体流程就基本分析完成了。当然Vue3响应式系统的肯定不止这些内容,Vue3对数组,Map,Set等特殊的object做了特殊的响应式处理,同时也提供了computed,ref,readonly,shallowReactive
等具有不同功能的响应式函数,虽然功能不同,但万变不离其宗,当我们理解了reactive
和effect
的流程,去理解剩下的响应式函数就轻松了许多。
当然,我这边狗屁不同的文章也不一定能帮大家理解Vue3响应式的原理,要想真正的理解还需大家亲自去看Vue3的源码,亲自调试,才能真正的理解Vue3的响应式系统原理。