本文受@蚂蚁保险体验技术的文章启发
写在前面
从单测可以让我们首先对代码逻辑又一个初步的认识,然后就是在单测覆盖率处于一个较高水准时,当我们在源码中遇到一些不太好理解的逻辑时就可以通过移除响应逻辑在执行测试看看是哪里抛出了异常,从而更好的理解源码逻辑。
本文主要分析vue3的响应式原理,源码涉及的ts类型推导及ts相关知识点非必要不会提及。
前置概念
Proxy
Proxy 对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。[Proxy MDN]
Vue3 响应式 API
请先浏览Vue3文档
Ref
我们介绍介绍下Ref涉及的工具函数
// 这几个函数看函数就知道意思了
import { isArray, isObject, hasChanged } from '@vue/shared'
// reactive暂时先不介绍
import { reactive, isProxy, toRaw, isReactive } from './reactive'
判断某个对象是否已经进行过响应式转化
function isProxy(value: unknown): boolean {
return isReactive(value) || isReadonly(value)
}
// 如果基础数据是Object类型,就调用reactive进行响应式转化,否则返回原数据
const convert = <T extends unknown>(val: T): T => isObject(val) ? reactive(val) : val;
Ref的接口定义
declare const RefSymbol: unique symbol
//Ref接口
export interface Ref<T = any> {
value: T //存放真正的数据
/**
* Type differentiator only.
* We need this to be in public d.ts but don't want it to show up in IDE
* autocomplete, so we use a private Symbol instead.
*/
[RefSymbol]: true // 标识是否是ref类型
/**
* @internal
*/
_shallow?: boolean //shallow标识
}
接下来我们按照函数的调用顺序看看我们创建一个ref对象的过程中都发生了什么。由于shallowRef和ref方法流程基本相同,所以我们这里已ref方法流程为例子介绍。
// 调用ref后会调用createRef创建Ref实例
export function ref(value?: unknown) {
return createRef(value)
}
function createRef(rawValue: unknown, shallow = false) {
// 如果已经是Ref类型的数据了就不用重复创建了
if (isRef(rawValue)) {
return rawValue
}
// 否侧创建并返回一个Ref实例,通过参数判断是否是shallow的
return new RefImpl(rawValue, shallow)
}
class RefImpl<T> {
private _value: T // 真实的value
public readonly __v_isRef = true //是否是Ref的标示,供isRef判断
// 传入readonly和shallow并初始化数据
constructor(private _rawValue: T, public readonly _shallow = false) {
this._value = _shallow ? _rawValue : convert(_rawValue)
}
get value() {
// 劫持get进行依赖收集
track(toRaw(this), TrackOpTypes.GET, 'value')
return this._value
}
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)
}
}
}
看到这里可能大家有个疑问,ref创建的过程中竟然还是调用了reactive方法其他时候都只是将value值设置为原始值,而且ref类型也没有使用Proxy进行代理,同时通过我们通过阅读官方文档也了解到reactive方法必须传入Object类型的对象这是为什么?这其实是是受限于Proxy,它仅能对Object类型进行劫持,这样就导致如果仅有reactive我们就只能实现Object类型的响应式数据,但是实际开发中存在我们希望基本数据类型也是响应式数据的情况,所以我们直接将简单数据类型包装为Ref类型后直接劫持get和set操作,这是第一点原因。还有另一点原因是基本数据类型在函数参数传递和结构过程中并不会传递引用,如果没有Ref会导致将reactive包装的响应式对象解构后就对失去响应式能力。这就需要toRefs方法来保证解构后的reactive对象仍具有响应式能力。
export function toRefs<T extends object>(object: T): ToRefs<T> {
// 如果不是reative对象开发环境做出警告
if (__DEV__ && !isProxy(object)) {
console.warn(`toRefs() expects a reactive object but received a plain one.`)
}
// 对数组或者对象的每项转换为ref
const ret: any = isArray(object) ? new Array(object.length) : {}
for (const key in object) {
ret[key] = toRef(object, key)
}
// 返回转化后的对象
return ret
}
export function toRef<T extends object, K extends keyof T>(
object: T,
key: K
): ToRef<T[K]> {
// 如果是Ref类型对象直接返回,否则返回新的ObjectRefImpl实例
return isRef(object[key])
? object[key]
: (new ObjectRefImpl(object, key) as any)
}
class ObjectRefImpl<T extends object, K extends keyof T> {
// readonly 其实知识初始化参数不同
public readonly __v_isRef = true
constructor(private readonly _object: T, private readonly _key: K) {}
// 这里get和set没有进行track和trigger
get value() {
return this._object[this._key]
}
set value(newVal) {
this._object[this._key] = newVal
}
}
这段代码其实是比较好理解的,唯一的疑问可能是为什么toRef返回的响应对象不需要在get和set中执行track和trigger方法呢?我们可以仔细观察这个ObjectRefImpl的实现,或者动用单测大法在proxy的get这个trap中随便输出点什么,执行单测我们就会发现,其实这里的这个_object并不是原数据的object,而是经过proxy代理的对象,所以这里的get操作不需要重新track,只需对_object操作就可以触发响应式对应的操作。
Reactive
引用函数
import { isObject, toRawType, def } from '@vue/shared' //isObject在Ref中已经见过,toRawType获得原始数据的类型,def是Object.defineProperty的封装
// 以下其实都是不同类型响应式数据使用Proxy所需要的trap
import {
mutableHandlers,
readonlyHandlers,
shallowReactiveHandlers,
shallowReadonlyHandlers
} from './baseHandlers'
import {
mutableCollectionHandlers,
readonlyCollectionHandlers,
shallowCollectionHandlers
} from './collectionHandlers'
//UnwrapRef是类型推断所需要的泛型,Ref就是前面所介绍的
import { UnwrapRef, Ref } from './ref'
枚举变量
// 响应式对象的一些标志
export const enum ReactiveFlags {
SKIP = '__v_skip',
IS_REACTIVE = '__v_isReactive',
IS_READONLY = '__v_isReadonly',
RAW = '__v_raw'
}
// 对象类型
const enum TargetType {
INVALID = 0,// 不支持的
COMMON = 1,// Object | Array
COLLECTION = 2// Map | Set | WeakMap | WeakSet
}
响应式对象的基本解构
// 可以看出基本的结构就是有标示和原数据组成
export interface Target {
[ReactiveFlags.SKIP]?: boolean
[ReactiveFlags.IS_REACTIVE]?: boolean
[ReactiveFlags.IS_READONLY]?: boolean
[ReactiveFlags.RAW]?: any
}
工具函数
// 判断一个对象是否是响应式对象
export function isReactive(value: unknown): boolean {
// 主要是通过标示来判断
if (isReadonly(value)) {
return isReactive((value as Target)[ReactiveFlags.RAW])
}
return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])
}
// 同样是通过标识
export function isReadonly(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
}
// 是否已经是一个被Proxy代理后的对象
export function isProxy(value: unknown): boolean {
return isReactive(value) || isReadonly(value)
}
// 将响应式数据转为原数据,如果不是响应式数据则返回原数据
export function toRaw<T>(observed: T): T {
return (
(observed && toRaw((observed as Target)[ReactiveFlags.RAW])) || observed
)
}
// 将特定数据标记为不需要响应式
export function markRaw<T extends object>(value: T): T {
def(value, ReactiveFlags.SKIP, true)
return value
}
核心实现
由于不同类别的响应式对象的核心实现基本相同主要是参数不同,所以核心实现仍以reactive为例子
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
// 如果所创建对象是一个只读的响应式对象,直接返回
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
// 调用createReactiveObject方法创建响应式对象
return createReactiveObject(
target,
false,// 是否readonly其实就是由传入参数判断
mutableHandlers,// 不同类型的响应式对象所使用handler有所不同
mutableCollectionHandlers
)
}
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>
) {
// reactive只接受对象不接受简单数据类型
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
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// 如果已经被proxy代理过,直接返回这个proxy
const proxyMap = isReadonly ? readonlyMap : reactiveMap
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 只有支持的类型才能被转化为响应式对象
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// 获取代理对象
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
// 保存已经转化的对象避免重复生成proxy
proxyMap.set(target, proxy)
return proxy
}
reactive内部的逻辑非常简单,就是创建响应式对象,真正的实现逻辑都在对应的handler中。
baseHandlers
外部引用
先来看看有哪些之前没见过的引用
// 前面reactive中用来保存已有proxy的集合
import {
readonlyMap,
reactiveMap
} from './reactive'
// Track 和 Trigger 类型的枚举值
import { TrackOpTypes, TriggerOpTypes } from './operations'
// 后面effect详细讲
import {
track,
trigger,
ITERATE_KEY,
pauseTracking,
resetTracking
} from './effect'
主要实现
同样不同响应式对象的handler有很多相似的地方,本文仅已mutableHandlers为例,其他不同的handler的特殊逻辑可以自行浏览package/reactivity/src/baseHandlers.ts
// proxy代理了以下几个trap
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys
}
// 按顺序看看各个trap是如何生成的
// 先来看看一些前置准备
// 首先我们要理解一下这个对象名,Instrumentation是一个计算机专业术语,指“插桩”,即指向某个方法被注入一段有其他作用的代码,目的就是为了劫持这些方法,增加相应逻辑,
const arrayInstrumentations: Record<string, Function> = {}
// instrument identity-sensitive Array methods to account for possible reactive
// values
// 翻译由于以下三个数组操作在比较时是要求严格相等的即(===),如果我们传入一个ref会无法正确得到响应的值,所以要将ref重新转为原始数据进行比较,案例情况下面
;(['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 + '')
}
// 首先使用原始参数调用方法,当原始参数是响应式参数是将响应式数据转化为原始数据
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)
// 这些操作会在修改的同时修改数组的length,要插桩来避免死循环
;(['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
}
})
identity-sensitive案例
const array = reactive([{},{}])
const observed = array[1]
isReactive(observed) // true
array.includes(observed) // true
array.lastIndexOf(observed) // 1
array.indexOf(observed) // 1
get
function createGetter(isReadonly = false, shallow = false) {
// 首先不同handler的get均通过createGetter创建,通过传入参数isReadonly和shallow来控制逻辑
return function get(target: Target, key: string | symbol, receiver: object) {
if (key === ReactiveFlags.IS_REACTIVE) {
// 如果是获取IS_REACTIVE标识,通过isReadonly来判断,因为readonly和reatvie由传入参数区分
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
// 同上
return isReadonly
} else if (
key === ReactiveFlags.RAW &&
receiver === (isReadonly ? readonlyMap : reactiveMap).get(target)
) {
// 返回代理对象
return target
}
// 判断原数据是否是数组
const targetIsArray = isArray(target)
// 如果响应式数据不为readonly并且target是数组,如果有插桩代理的操作则用Reflect反应到原数据上
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
// 使用Reflect而不使用object[key]是因为有些时候get可能会涉及this
const res = Reflect.get(target, key, receiver)
// 内置方法不做依赖收集,因为访问内置方法之前必然会访问target会触发get
if (
isSymbol(key)
? builtInSymbols.has(key as symbol)
: key === `__proto__` || key === `__v_isRef`
) {
return res
}
// 只读不做依赖收集
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
// shallow的直接返回原数据
if (shallow) {
return res
}
if (isRef(res)) {
// 对Ref进行解套
// ref unwrapping - does not apply for Array + integer key.
const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
return shouldUnwrap ? res.value : res
}
// 如果是属性是Object类型则进行对应的响应式转化
// 这样操作有几个原因,首先因为proxy仅能代理第一层对象深度嵌套无法代理,每次当需要使用深层嵌套时才做响应式转发以达到lazy的目的提高性能
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
}
}
set
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
const oldValue = (target as any)[key]
if (!shallow) {
// 当不是shallow模式时,如果value是响应式数据,取value的元数据
value = toRaw(value)
// 当设置目标值是Ref时,继续保持目标值为Ref,修改Ref的value
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
}
} else {
// in shallow mode, objects are set as-is v of reactive or not
}
// 判断是新增属性还是修改原有属性
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
// Reflect反射到原目标
const result = Reflect.set(target, key, value, receiver)
// don't trigger if target is something up in the prototype chain of original
// 这里的判断是由于当proxy是原型链的某一个节点时,receiver可能不指向proxy本身
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
}
}
其他trap
其他trap没有什么需要特别说明的逻辑,可以自行浏览源码
collectionHandlers
插桩读
集合类即Set,Map,WeakSet,WeakMap为什么需要一个特殊的handler来处理呢,因为这些类的内部实现依赖this,但是当我们完成响应式转化后,这个this其实指向的是proxy对象.这就需要我们重新绑定this
// 一个插桩操作来处理特殊逻辑
export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {
get: createInstrumentationGetter(false, false)
}
// 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) {
!isReadonly && track(rawTarget, TrackOpTypes.GET, key)
}
!isReadonly && track(rawTarget, TrackOpTypes.GET, rawKey)
const { has } = getProto(rawTarget)
const wrap = isReadonly ? toReadonly : isShallow ? toShallow : toReactive
if (has.call(rawTarget, key)) {
return wrap(target.get(key))
} else if (has.call(rawTarget, rawKey)) {
return wrap(target.get(rawKey))
}
}
// 插桩,通过创建一个和Set和Map具有相同方法的对象来规避无法对Set和Map的set进行代理的情况
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)
}
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
const instrumentations = shallow
? shallowInstrumentations
: isReadonly
? readonlyInstrumentations
: mutableInstrumentations
return (
target: CollectionTypes,
key: string | symbol,
receiver: CollectionTypes
) => {
// 如果是key是标识,逻辑和baseHandlers相同
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
} else if (key === ReactiveFlags.RAW) {
return target
}
return Reflect.get(
// 如果有插桩对象中有此key,且目标对象也有此key,那就用这个插桩对象做反射get的对象,否则用原始对象
hasOwn(instrumentations, key) && key in target
? instrumentations
: target,
key,
receiver
)
}
}
插桩迭代器
function createIterableMethod(
method: string | symbol,
isReadonly: boolean,
isShallow: boolean
) {
return function(
this: IterableCollections,
...args: unknown[]
): Iterable & Iterator {
// 枚举器相应逻辑
const target = (this as any)[ReactiveFlags.RAW]
const rawTarget = toRaw(target)
const targetIsMap = isMap(rawTarget)
// 如果是entries方法,或者是map的迭代方法的话,isPair为true
// 这种情况下,迭代器方法的返回的是一个[key, value]的结构
const isPair =
method === 'entries' || (method === Symbol.iterator && targetIsMap)
const isKeyOnly = method === 'keys' && targetIsMap
// 调用原型链上的相应迭代器方法
const innerIterator = target[method](...args)
// 获取相应的转成响应数据的方法
const wrap = isReadonly ? toReadonly : isShallow ? toShallow : toReactive
// 收集依赖
!isReadonly &&
track(
rawTarget,
TrackOpTypes.ITERATE,
isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY
)
// return a wrapped iterator which returns observed versions of the
// values emitted from the real iterator
// 给返回的innerIterator插桩,将其value值转为响应式数据
return {
// iterator protocol
next() {
const { value, done } = innerIterator.next()
return done
? { value, done }
: {
value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value),
done
}
},
// iterable protocol
[Symbol.iterator]() {
return this
}
}
}
}
插桩写操作
写操作比较简单
function add(this: any, value: any) {
// 获取原始数据
value = toRaw(value)
const target = toRaw(this)
// 获取原型
const proto: any = Reflect.getPrototypeOf(this)
// 通过原型方法,判断是否有这个key
const hadKey = proto.has.call(target, value)
// 通过原型方法,增加这个key
const result = proto.add.call(target, value)
// 原本没有key的话,说明真的是新增,则触发监听响应逻辑
if (!hadKey) {
/* istanbul ignore else */
if (__DEV__) {
trigger(target, OperationTypes.ADD, value, { value })
} else {
trigger(target, OperationTypes.ADD, value)
}
}
return result
}
effect
代码看到这一步,其实困惑我们的问题已经所剩无几。只剩下最后的依赖是如何收集的,又是如何触发监听函数这两个问题了,也即track和trigger这两个函数的内部逻辑
外部引入
// track 和 trigger option的枚举值
import { TrackOpTypes, TriggerOpTypes } from './operations'
// 这几个函数就是简单的字面意思
import { EMPTY_OBJ, isArray, isIntegerKey, isMap } from '@vue/shared'
interface
// 这是effect的类型
export interface ReactiveEffect<T = any> {
(): T // 监听函数
_isEffect: true //是否是effect标识
id: number // effect唯一id
active: boolean // effect 是否激活
raw: () => T // 监听的原始函数
deps: Array<Dep> // 依赖集
options: ReactiveEffectOptions // effect的一些配置
allowRecurse: boolean // 是否允许递归,划重点vue3默认是不会让effect陷入死循环的
}
// effect 所传参数
export interface ReactiveEffectOptions {
lazy?: boolean // 是否延迟计算,默认状态effect创建后就会立即执行
scheduler?: (job: ReactiveEffect) => void // 调度器函数,接受的入参run即是传给effect的函数,如果传了scheduler,则可通过其调用监听函数。
// 两个调试函数
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
// 通过stop操作终止监听后触发的事件,vue3可以中断监听
onStop?: () => void
allowRecurse?: boolean // 是否允许递归
}
// debug类型....
export type DebuggerEvent = {
effect: ReactiveEffect
target: object
type: TrackOpTypes | TriggerOpTypes
key: any
} & DebuggerEventExtraInfo
工具函数
// 通过标识判断是否是effecft
export function isEffect(fn: any): fn is ReactiveEffect {
return fn && fn._isEffect === true
}
核心逻辑
// 存放effect的数组
export const effectStack: ReactiveEffect[] = []
// 当前激活的effect
let activeEffect: ReactiveEffect | undefined
export function effect<T = any>(
fn: () => T,
options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
// 如果传入的已经是一个effect直接返回原监听函数
if (isEffect(fn)) {
fn = fn.raw
}
// 创建effect
const effect = createReactiveEffect(fn, options)
if (!options.lazy) {
// 如果不特殊设置lazy, effect会在创建后立即执行一次监听函数
effect()
}
return effect
}
function createReactiveEffect<T = any>(
fn: () => T,
options: ReactiveEffectOptions
): ReactiveEffect<T> {
const effect = function reactiveEffect(): unknown {
// 当调用stop暂停effect时的处理逻辑
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的一些属性
effect.id = uid++
effect.allowRecurse = !!options.allowRecurse
effect._isEffect = true
effect.active = true
effect.raw = fn
effect.deps = []
effect.options = options
return effect
}
effect函数中的if(!effectStack.includes(effect))就是用来避免当effect中的依赖被递归的重复更新,当重复更新时,effectStack已经包含当前effect就不会再重复执行监听函数来
接下来对上面过程出现的几个函数作分析
// stop方法
export function stop(effect: ReactiveEffect) {
if (effect.active) {
// 如果当前effect处于激活状态执行cleanup方法
cleanup(effect)
// 触发onStop事件
if (effect.options.onStop) {
effect.options.onStop()
}
// 更新effect状态
effect.active = false
}
}
// cleanup
function cleanup(effect: ReactiveEffect) {
const { deps } = effect
// 当前effect的dep不为空时,清除存储了当前effect的引用
if (deps.length) {
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect)
}
// 将依赖重置为空
deps.length = 0
}
}
接下来终于要解答我们最后得到疑惑了,track和trigger到底做了什么
在了解track和trigger之前,我们首先需要了解下依赖的存储结构
// 调换个声明顺序方便理解
const targetMap = new WeakMap<any, KeyToDepMap>()
type KeyToDepMap = Map<any, Dep>
type Dep = Set<ReactiveEffect>
我们将这个多维的数据扁平化一下,它大概是这个样子WeakMap<Target, Map<string | symbol, Set<ReactiveEffect>>>
target的我们都知道是原数据,一个target映射到一张MAP,这张Map保存着当前target某个属性和这个属性的依赖集合
了解了依赖的存储结构我们再来看看track的具体实现。
track
export function track(target: object, type: TrackOpTypes, key: unknown) {
// 当前没有激活的effect,说明当前依赖为空,直接返回
if (!shouldTrack || activeEffect === undefined) {
return
}
// 从targetMap获取target对应Map
let depsMap = targetMap.get(target)
// 不存在则初始化改Map
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
// 从Map中获取key对应的依赖集合
let dep = depsMap.get(key)
// 不存在的话初始化该集合
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
// 如果key的依赖集合中没有当前集合的effect
if (!dep.has(activeEffect)) {
// 将当前激活的effect添加到依赖集合
dep.add(activeEffect)
// 并在当前激活集合的dep中也添加当前的依赖集合
activeEffect.deps.push(dep)
// 调试代码
if (__DEV__ && activeEffect.options.onTrack) {
activeEffect.options.onTrack({
effect: activeEffect,
target,
type,
key
})
}
}
}
这段代码中有很多地方都一下让人想不通。
首先为什么当前的activeEffect就是依赖当前target的监听函数呢?
是由下面这段代码保证的,首先将effect推入栈执行了fn(),这个函数就是依赖target的监听函数,这个函数理所当然的引用了target,这就触发了target的get,进而会执行track函数,此时的activeEffect就是该effect
try {
enableTracking()
effectStack.push(effect)
activeEffect = effect
return fn()
} finally {
effectStack.pop()
resetTracking()
activeEffect = effectStack[effectStack.length - 1]
}
targetMap到底咋回事
1.targetMap中存着每个target的依赖映射表,这张表中存有target的每个属性和依赖这个属性的所有effect的集合dep的映射关系
2. 每个effect的dep中存有所有包含自身的effect集合(即1中所说dep)的集合
我们可以从一张表格看看targetMap的结构
刚刚我们又说道cleanup函数会将effect自身dep属性清空,以次来在每次监听函数触发get前把自身从依赖中删除,再触发完track后再重新将自身添加会依赖集合,这是为了处理监听函数中带有分支处理的情况,因为每次的依赖都有可能不同,所以需要每次更新依赖。即如下情况
const conditionalSpy = jest.fn(() => {
dummy = obj.run ? obj.prop : 'other'
})
effect(conditionalSpy)
我们从流程图来看看依赖是如何收集的
trigger
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
// 如果当前target没有任何key有被依赖,那什么都不用做
const depsMap = targetMap.get(target)
if (!depsMap) {
// never been tracked
return
}
// 需要触发的effect集合
const effects = new Set<ReactiveEffect>()
// 将某个effect结合中所有不是当前activeEffect,或者允许递归的effect加入到effects
const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect || effect.allowRecurse) {
effects.add(effect)
}
})
}
}
// 如果操作类型是clear,原始target的所有监听函数都需要被执行
if (type === TriggerOpTypes.CLEAR) {
// collection being cleared
// trigger all effects for target
depsMap.forEach(add)
} else if (key === 'length' && isArray(target)) {
// 如果target是数组且变更属性是length,所所有依赖length属性的监听函数都需要执行
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= (newValue as number)) {
add(dep)
}
})
} else {
// schedule runs for SET | ADD | DELETE
// 当key存在时,操作一定是set、add、delete中的一种
if (key !== void 0) {
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)) {
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:
// set
if (isMap(target)) {
add(depsMap.get(ITERATE_KEY))
}
break
}
}
const run = (effect: ReactiveEffect) => {
// debug相关
if (__DEV__ && effect.options.onTrigger) {
effect.options.onTrigger({
effect,
target,
key,
type,
newValue,
oldValue,
oldTarget
})
}
if (effect.options.scheduler) {
// 放进scheduler调度
effect.options.scheduler(effect)
} else {
// 没有调度直接执行
effect()
}
}
// 逐个执行所有effect
effects.forEach(run)
}
从流程图看下trigger做了什么