数据响应式是什么?
数据响应式是一种机制,能够侦测到数据的变化,然后响应这个变化到视图
而Vue3设计理念是声明式(Declarative)开发,最大的好处:数据驱动,不用关心dom,只用关心状态数据
Vue2使用Object.defineProperty实现响应式
Vue3使用Proxy实现响应式
来看看两者的区别:
Object.defineProperty:
Proxy:
这只是一小部分的优化,而在Vue3.2.0,迎来了一个很强大的响应式性能优化,这个之后会细说(使用位运算)
那么到了这里,我们正式开始分析Vue3的响应式模块
reactive方法 路径: core-main\packages\reactivity\src\reactive.ts
// From setup
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.
// 如果是只读的,那就不需要进行依赖收集,直接返回
if (isReadonly(target)) {
return target
}
// To: createReactiveObject
// 返回createReactiveObject方法调用结果
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
}
通常,我们都会使用reactive和ref方法将数据进行响应式化,我们先从reactive开始分析
reactive方法做了什么?
reactive方法只是做了一个判断: 判断传入的target是否是只读的
如果是只读的,就直接原样返回
如果不是只读的,调用createReactiveObject方法并返回其执行结果
从createReactiveObject这个命名大概能猜出,这个方法的功能是创建一个响应式对象
那么来看此方法的实现
createReactiveObject方法 路径: core-main\packages\reactivity\src\reactive.ts
// From reactive:
// Reutrn To reactive: 返回经过proxy代理的对象,这个proxy代理对象内部的方法取决于handlers
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
// 如果不是对象,直接返回
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
// 如果目标已经是一个代理(响应式对象),返回它。
// 例外:在reactive对象上调用 readonly() 不返回
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// target already has corresponding Proxy
// 如果对象在proxyMap上,直接返回,防止反复创建代理对象
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// only a whitelist of value types can be observed.
// 只能观察到值类型的白名单。
// To: getTargetType:
// Return From getTargetType: 获取target的类型
// INVALID是除了Object Array Map Set WeakMap WeakSet之外的类型,也就是除了这类型之外的类型,返回
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// 执行Proxy代理,对target对象进行代理
// 会根据TargetType.COLLECTION判断使用哪个handlers进行代理
// baseHandlers是一般值类型 collectionHandlers是map set这些类型
// From: createReactiveObject
// To: baseHandlers
const proxy = new Proxy(
target,
// From: createReactiveObject
// To: baseHandlers
// Return From baseHandlers: 返回包含get、set、deleteProperty、ownKeys、has方法的对象
// To: collectionHandlers
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
// 代理完之后,在proxyMap上收集代理对象,防止反复创建代理对象
proxyMap.set(target, proxy)
// 返回proxy代理对象 代理对象上的方法取决于handlers
return proxy
}
createReactiveObject方法做了什么?
经过一系列判断后,创建并返回一个代理对象
这个代理对象上的方法来自于第二个参数,而第二个参数是什么要取决于类型:
想要知道我们的代理对象上的方法有哪些,我们先从基础普通类型开始,先来看baseHandlers
mutableHandlers对象 路径: core-main\packages\reactivity\src\baseHandlers.ts
// From createReactiveObject:
// Return To createReactiveObject: 返回如下方法
export const mutableHandlers: ProxyHandler
调用reactive方法传过来的是mutablehandlers对象
可以看到,这个对象拥有五个方法: get、set、deleteProperty、has、ownKeys
所以代理对象上拥有这五个方法
get: 触发自读取操作
set: 触发自赋值操作
deleteProperty: 触发自 delete操作
has: 触发自 in 操作
ownKeys: 触发自 for in 操作
我们一个个来看这些方法
get来自createGetter方法 路径: core-main\packages\reactivity\src\baseHandlers.ts
// From get:
function createGetter(isReadonly = false, shallow = false) {
// 返回get方法
return function get(target: Target, key: string | symbol, receiver: object) {
// 如果key已经是reactive,返回!isReadonly
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
// 如果key已经是readonly,返回isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
// 如果key是shallow,返回shallow
} else if (key === ReactiveFlags.IS_SHALLOW) {
return shallow
} else if (
// 用于给Set中屏蔽原型引起的更新(之后会细说)
key === ReactiveFlags.RAW &&
receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap
).get(target)
) {
return target
}
// From createGetter:
// To isArray:
// Retrun From isArray: 返回Array.isArray 判断是否是数组类型
// 判断target是否是数组
const targetIsArray = isArray(target)
// From: createGetter:
// To hasOwn:
// Return From hasOwn: 判断对象是否有指定的属性
// 如果不是只读并且 target是数组类型 并且key存在于arrayInstrumentations上
// 那么返回定义在arrayInstrumentations上的方法 也就是重写的数组方法
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
// 返回数组方法
// From: createGetter:
//todo To: arrayInstrumentations
// 例如: 当执行arr.includes其实执行的是arrayInstrumentations.includes 这样就实现了重写数组方法
return Reflect.get(arrayInstrumentations, key, receiver)
}
// 使用Reflect是为了第三个参数的this
const res = Reflect.get(target, key, receiver)
// From crateGetter:
// To isSymbol:
// Return From isSymbol: 判断是否是Symbol类型
// To isNonTrackableKeys:
// Return From isNonTrackableKeys: 判断是否是非跟踪类型 __proto__,__v_isRef,__isVue
// 不应该在副作用函数与Symbol这类值之间建立响应联系,
// 如果key的类型是symbol,不需要收集依赖,返回
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res
}
// 如果不是只读,才需要收集依赖,建立响应联系
if (!isReadonly) {
// From createGetter:
// To: track
// Return From track: 创建依赖集合,trackEffects收集依赖
track(target, TrackOpTypes.GET, key)
}
// 如果是shallow浅响应式,返回经过一次依赖收集的res
if (shallow) {
return res
}
// From createGetter:
// To: isRef
// Return From isRef: 判断r上是否有__v_isRef属性,判断是否是Ref
// 如果是Ref,脱Ref
if (isRef(res)) {
// ref unwrapping - does not apply for Array + integer key.
// ref unwrapping - 不适用于 Array + integer key。
const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
// 脱ref
return shouldUnwrap ? res.value : res
}
// From createGetter:
// To isObject:
// Return From isObject: 判断是否是对象类型
// 如果是对象,根据readonly来决定怎么递归,深只读,深reactive
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.
// 也将返回值转换为代理。 我们进行 isObject 检查
// 这里是为了避免无效值警告。 还需要惰性访问只读
// 并在此处进行反应以避免循环依赖。
// From createGetter:
//todo To: readonly
return isReadonly ? readonly(res) : reactive(res)
}
// 最后返回res
return res
}
}
createGetter方法做了什么?
get内部除了一些判断之外,还会对数组方法进行重写,会调用track收集依赖,会根据是否是对象去递归readonly或reactive
收集依赖,耳熟能详的词啊,那就来看看这收集依赖是个什么样子吧
track方法 路径: core\packages\reactivity\src\effect.ts
// From createGetter:
export function track(target: object, type: TrackOpTypes, key: unknown) {
// 判断shouldTrack和activeEffect
if (shouldTrack && activeEffect) {
// 这一整步是为了创建依赖集合,像这样的结构 targetMap: {target -> key -> dep -> effect}
// targetMap是WeakMap类型,depsMap是Map类型,dep是Set类型
// targetMap的key值是target,value值是depsMap
// depsMap的key值是key,value值是dep
// dep的key值是effect,也就是ReactiveEffect
// 因此形成了相对应的依赖集合,这个会细说
let depsMap = targetMap.get(target)
// 不存在时,初始化依赖集合
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
// 不存在时,初始化依赖集合
if (!dep) {
// From track:
// To createDep:
// Return From createDep: 创建一个set集合,并且set集合有两个关于响应式性能优化的属性w和n
depsMap.set(key, (dep = createDep()))
}
// DEV忽略
const eventInfo = __DEV__
? { effect: activeEffect, target, type, key }
: undefined
// 执行trackEffects,进行依赖的添加
trackEffects(dep, eventInfo)
}
}
track方法做了什么?
track内部最主要的一件事就是创建和获取依赖集合了
所谓的依赖集合,是一个三层结构的集合 target -> key -> dep -> effect,这样的依赖集合可以很清晰的知道依赖的对应关系
targetMap是WeakMap类型,depsMap是Map类型,dep是Set类型
创建获取依赖集合后,会执行trackEffects方法,来看其实现
trackEffects方法 路径: core\packages\reactivity\src\effect.ts
// From track:
// Return To track: 将activeEffect添加到dep中,并且将dep添加到activeEffect.deps中,deps就是一个与当前副作用函数存在联系的依赖集合,为了在清理时能够知道是否需要清理
export function trackEffects(
dep: Dep,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
// 将shouldTrack置为false
let shouldTrack = false
//todo: 涉及响应式性能优化,后面一起来看,先不管,看else
if (effectTrackDepth <= maxMarkerBits) {
if (!newTracked(dep)) {
dep.n |= trackOpBit // set newly tracked
shouldTrack = !wasTracked(dep)
}
} else {
// Full cleanup mode.
// 完全清理模式。
// 如果Dep有activeEffect,shouldTrack为false,代表不用收集,如果没有,则需要收集
shouldTrack = !dep.has(activeEffect!)
}
// 判断shouldTrack
if (shouldTrack) {
// 如果shouldTrack为true,则将activeEffect添加到dep中
dep.add(activeEffect!)
// 并且将dep添加到activeEffect.deps中, deps就是一个与当前副作用函数存在联系的依赖集合,为了在清理时能够知道是否需要清理
activeEffect!.deps.push(dep)
// DEV忽略
if (__DEV__ && activeEffect!.onTrack) {
activeEffect!.onTrack(
Object.assign(
{
effect: activeEffect!
},
debuggerEventExtraInfo
)
)
}
}
}
trackEffects方法做了什么?
可以看到,trackEffects才是真正的将依赖收集起来的方法!
所以track会创建依赖集合确定对应关系,trackEffects将依赖添加到dep中,完成依赖收集
知道了track方法会收集依赖之后,我们回来看数组方法的重写
在createGetter中,还留下了一个todo: 如果不是只读,并且target是数组类型,并且arrayInstrumentations对象上拥有对应的key,这种情况就是对数组的方法进行处理了
现在来看createArrayInstrumentations方法
createArrayInstrumentations方法 路径: core\packages\reactivity\src\baseHandlers.ts
// From createGetter:
// Return To createGetter: // 返回一个包含了重写的几个数组方法'push', 'pop', 'shift', 'unshift', 'splice','includes', 'indexOf', 'lastIndexOf'的对象
function createArrayInstrumentations() {
const instrumentations: Record<string, Function> = {}
// instrument identity-sensitive Array methods to account for possible reactive
// values
// includes indexOf lastIndexOf它们都根据给定的值返回查找结果
;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
// this是代理数组
instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
// 获取原始数组
const arr = toRaw(this) as any
// 循环代理数组
for (let i = 0, l = this.length; i < l; i++) {
// 对原始数组的每个值进行track依赖收集
track(arr, TrackOpTypes.GET, i + '')
}
// we run the method using the original args first (which may be reactive)
// 执行includes indexOf lastIndexOf方法,将返回值赋值给res
const res = arr[key](...args)
// 如果返回值是-1或false,代表没找到
if (res === -1 || res === false) {
// if that didn't work, run it again using raw values.
// 如果不起作用,返回用原始值调用的结果
return arr[key](...args.map(toRaw))
} else {
// 如果找到了,返回方法的返回结果res
return res
}
}
})
// instrument length-altering mutation methods to avoid length being tracked
// which leads to infinite loops in some cases (#2137)
// 会隐式修改数组长度的方法 当调用push时,会读取数组的length属性值,也会设置数组的length属性值,会导致两个独立的副作用函数互相影响,会导致栈溢出
// 所以只要屏蔽这些方法对length属性值的读取,就可以了
;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
// this是代理数组
instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
// From: createArrayInstrumentations
// To pauseTracking:
// Return From pauseTracking: 停止依赖收集
pauseTracking()
// toRaw获取原始数组,并执行相应方法 push pop shift unshift splice,将结果赋值给res
const res = (toRaw(this) as any)[key].apply(this, args)
// From: createArrayInstrumentations
// To resetTracking:
// Return From resetTracking: 重启依赖收集
resetTracking()
// 将执行结果res返回
return res
}
})
// 返回instrumentations对象,包含了重写的几个数组方法'push', 'pop', 'shift', 'unshift', 'splice','includes', 'indexOf', 'lastIndexOf'
return instrumentations
}
重写方法做了什么?
这一步的注释在: 数组方法重写
此时get看完了,来看set
createSetter方法 路径: core\packages\reactivity\src\baseHandlers.ts
// From set:
// Return To Set: 返回set方法,内部会触发依赖,执行相关联的副作用函数
function createSetter(shallow = false) {
// 返回set方法
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
// 获取旧值
let oldValue = (target as any)[key]
// From createSetter:
// To isReadonly:
// Return From isReadonly: 判断是否是只读类型 根据据value上是否有ReactiveFlags.IS_READONLY属性判断
// To isRef:
// Return From isRef: 判断是否是Ref类型 根据value上是否有__v_isRef属性判断
// 如果是只读并且Ref并且新值不是Ref,返回false
if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
return false
}
// 如果shallow是false(浅响应式),并且新值不是只读
if (!shallow && !isReadonly(value)) {
// From createSetter:
// To isShallow:
// Return From isShallow: 判断是否是浅响应式 根据value上是否有ReactiveFlags.IS_SHALLOW属性判断
// 如果新值也不是浅响应式
if (!isShallow(value)) {
// From createSetter:
// To: toRaw
// Return From toRaw: 返回原始的代理对象
value = toRaw(value)
oldValue = toRaw(oldValue)
}
// 如果target不是数组,并且老值是Ref并且新值不是Ref
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
// 直接把新值赋值给老值的value
oldValue.value = value
return true
}
} else {
// in shallow mode, objects are set as-is regardless of reactive or not
// 在浅模式下,无论是否反应,对象都按原样设置
}
const hadKey =
// From createSetter:
// To isIntegerKey:
// Return From isIntegerKey: 判断是否是数字索引
// 如果target是数组,并且key是数字(索引)
isArray(target) && isIntegerKey(key)
? // 如果索引小于数组长度,代表没有新增,就是SET类型,如果不小于数组长度,代表新增了,就是ADD类型
Number(key) < target.length
: // 如果拥有对应的key,是SET类型,如果没有对应的key,代表要新增,就是ADD类型
hasOwn(target, key)
// 使用Reflect.set方法,receiver为了this
const result = Reflect.set(target, key, value, receiver)
// don't trigger if target is something up in the prototype chain of original
// target === toRaw(receiver)就说明receiver就是target的代理对象,此目的是为了屏蔽由原型引起的更新
if (target === toRaw(receiver)) {
if (!hadKey) {
// From createSetter:
// To: trigger
// Return From trigger: 将相对应的副作用函数(effect)推入到deps数组中,然后triggerEffects去遍历执行副作用函数
// 如果hadkey为false,那么Trigger类型为ADD
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
// From createSetter:
// To hasChanged:
// Return From hasChanged: 比较新值和旧值是否发生了变化,包含对 NaN的判断。
// 如果新旧值发生了变化,那么以SET操作执行trigger,以key做关联
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
// 返回Reflect.set方法的返回值
return result
}
}
createSetter方法做了什么?
set内部会触发trigger触发依赖
trigger方法 路径: core\packages\reactivity\src\effect.ts
// From createSetter:
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
// 获取depsMap
const depsMap = targetMap.get(target)
// 如果depsMap不存在
if (!depsMap) {
// never been tracked
// 从未被追踪,返回
return
}
// 创建deps数组
let deps: (Dep | undefined)[] = []
// 如果type为CLEAR
if (type === TriggerOpTypes.CLEAR) {
// collection being cleared
// trigger all effects for target
// 集合被清空
// 触发所有的依赖
// 展开target里的所有值,并values执行get,触发所有依赖
deps = [...depsMap.values()]
// 如果target是数组并且修改了数组的length属性
} else if (key === 'length' && isArray(target)) {
// 遍历副作用函数,找出需要的副作用函数
depsMap.forEach((dep, key) => {
// 只有索引值大于等于length时,才需要添加到deps数组中 例如 一个数组有5个元素,你通过arr.length = 3改变数组,此时数组需要更新,但是arr.length = 7,不需要更新
if (key === 'length' || key >= (newValue as number)) {
deps.push(dep)
}
})
} else {
// schedule runs for SET | ADD | DELETE
if (key !== void 0) {
deps.push(depsMap.get(key))
}
// also run for iteration key on ADD | DELETE | Map.SET
switch (type) {
// 当类型为ADD时,新增
case TriggerOpTypes.ADD:
// 如果target不是数组
if (!isArray(target)) {
// ADD操作会使对象的键变多,会影响到for in循环的此处,因此取出与ITERATE_KEY关联的副作用函数,推入到deps数组中 (这里的ITERATE_KEY涉及ownKeys for in循环)
deps.push(depsMap.get(ITERATE_KEY))
// 如果target是map类型
if (isMap(target)) {
// ADD操作会使map的size属性变化,因此取出与MAP_KEY_ITERATE_KEY关联的副作用函数,推入到deps数组中
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
}
} else if (isIntegerKey(key)) {
// target是数组,并且key是正整数
// ADD操作会使数组的length属性变化,因此取出与length属性相关的副作用函数,推入到deps数组中
// new index added to array -> length changes
deps.push(depsMap.get('length'))
}
break
// 当类型为DELETE时
case TriggerOpTypes.DELETE:
// 如果target不是数组
if (!isArray(target)) {
// DELETE操作会使对象的键变少,会影响到for in循环的次数, 因此又要取出取出与ITERATE_KEY关联的副作用函数,推入到deps数组中
deps.push(depsMap.get(ITERATE_KEY))
// 如果target是map类型
if (isMap(target)) {
// DELETE操作会使map的size属性变化,因此取出与MAP_KEY_ITERATE_KEY关联的副作用函数,推入到deps数组中
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
// 当类型为SET时
case TriggerOpTypes.SET:
// 如果target是map类型
if (isMap(target)) {
// SET操作会使map的变化,因此取出与ITERATE_KEY关联的副作用函数,推入到deps数组中
deps.push(depsMap.get(ITERATE_KEY))
}
break
}
}
// DEV忽略
const eventInfo = __DEV__
? { target, type, key, newValue, oldValue, oldTarget }
: undefined
// 判断deps数组是否只有一个,也就是只有一个副作用函数
if (deps.length === 1) {
if (deps[0]) {
// DEV忽略
if (__DEV__) {
triggerEffects(deps[0], eventInfo)
} else {
// 执行triggerEffects
triggerEffects(deps[0])
}
}
} else {
// 如果不止一个副作用函数
const effects: ReactiveEffect[] = []
// 遍历deps数组
for (const dep of deps) {
if (dep) {
// 将dep推入到effects中
effects.push(...dep)
}
}
// DEV忽略
if (__DEV__) {
triggerEffects(createDep(effects), eventInfo)
} else {
// 涉及到set规范 执行triggerEffects
triggerEffects(createDep(effects))
}
}
}
trigger方法做了什么?
所以trigger方法就是将相对应变化的操作关联的副作用函数添加到deps数组中,然后将deps数组交给triggerEffects方法
这一步的注释在trigger
triggerEffects方法 路径: core\packages\reactivity\src\effect.ts
// From trigger:
// Return To createSetter: 循环所用的副作用函数,根据有无调度器,使用不同方式执行副作用函数
export function triggerEffects(
dep: Dep | ReactiveEffect[],
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
// spread into array for stabilization
// 找到相关依赖,循环所有的副作用函数
for (const effect of isArray(dep) ? dep : [...dep]) {
// 如果trigger触发执行的副作用函数与现在正在执行的副作用函数相同,则不触发执行
if (effect !== activeEffect || effect.allowRecurse) {
// DEV忽略
if (__DEV__ && effect.onTrigger) {
effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
}
// 如果一个副作用函数存在调度器
if (effect.scheduler) {
// 则调用该调度器,并将副作用函数作为参数传递
effect.scheduler()
} else {
// 否则直接执行副作用函数
effect.run()
}
}
}
}
triggerEffects方法做了什么?
现在知道triggerEffects会取出对应的副作用函数,并将其执行就可以了,effect.run先留着,之后来说
这一步的注释在triggerEffects
get会对那几个数组方法进行重写,会执行track创建获取依赖集合,会执行trackEffects进行响应式性能优化(todo)并进行依赖收集
set,会执行trigger经过一系列判断,将相对应变化的依赖推入到deps数组中,triggerEffects会对deps数组中的依赖的副作用函数进行执行(通过effect.run或scheduler todo)
这一步的注释在: 返回get、set
此时mutableHandlers的get和set方法已经看完,现在来看剩下的方法:has、ownkeys、deleteProperty
has方法 路径: core\packages\reactivity\src\baseHandlers.ts
// From mutableHandlers:
// Return To mutableHandlers: 如果不是Symbol值,就以HAS类型执行track收集依赖,并返回has操作的结果(has操作来自 in操作符)
function has(target: object, key: string | symbol): boolean {
// key是否在target上,如果在,返回true,否则返回false
const result = Reflect.has(target, key)
// From has:
// To: builtInSymbols
// Return From builtInSymbols: 13个内置的Symbol值
// 如果key不是Symbol类型 或者 key不在builtInSymbols集合上
if (!isSymbol(key) || !builtInSymbols.has(key)) {
// 以HAS类型进行track依赖收集,关联为key
track(target, TrackOpTypes.HAS, key)
}
// 返回has操作的结果
return result
}
has方法是in操作符触发的,做了什么?
这一步的注释在: has
deleteProperty方法 路径: core\packages\reactivity\src\baseHandlers.ts
// From mutableHandlers:
// Return To mutableHandlers: 如果target有相应的key值,并且删除成功,就以DELETE类型执行trigger触发依赖,key为关联,并将删除的老值传过去,并返回deleteProperty的操作结果
function deleteProperty(target: object, key: string | symbol): boolean {
// hasOwn target上是否有相应的key,有则ture,无则false
const hadKey = hasOwn(target, key)
// 获取key值赋值给oldValue
const oldValue = (target as any)[key]
// 执行Reflect.deleteProperty操作删除key属性,并将返回值返回给result
const result = Reflect.deleteProperty(target, key)
// 如果hadKey为true result为true,代表有相应的key并成功删除
if (result && hadKey) {
// 以DELETE类型执行trigger触发依赖,key为关联,并将删除的老值传过去
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
}
// 返回deleteProperty的操作结果
return result
}
deleteProperty由delete操作触发,做了什么?
这一步的注释在: deleteProperty
ownKeys方法 路径: core\packages\reactivity\src\baseHandlers.ts
// From mutableHandlers:
// Return To mutableHandlers: 以ITERATE类型执行track收集依赖,如果操作目标是数组,则使用length属性作为key建立关联,如果不是数组是对象,则使用ITERATE_KEY建立关联
function ownKeys(target: object): (string | symbol)[] {
// 以ITERATE类型执行track收集依赖
// 如果操作目标是数组,则使用length属性作为key建立关联,如果不是数组,则使用ITERATE_KEY建立关联
// 对象时,删除和增加属性值都会影响for in循环,所以用ITERATE_KEY为key做关联
// 但是数组不一样,数组添加新元素或者修改长度都会影响for in循环,而添加新元素和修改长度都是修改length属性,因此要用length属性为key建立关联
track(target, TrackOpTypes.ITERATE, isArray(target) ? 'length' : ITERATE_KEY)
// 返回ownkeys的操作结果
return Reflect.ownKeys(target)
}
ownKeys由for in触发,做了什么?
这一步的注释在: ownKeys
此时五个方法都看完了,总结如下:
get(触发自读取操作): 触发track创建获取依赖集合,与副作用函数建立联系,能清楚的知道副作用函数的对应关系,然后通过trackEffects将其添加到deps中,进行依赖收集
set(触发自赋值操作): 触发trigger获取对应的副作用函数,根据类型判断是什么操作,添加相关的副作用函数到deps中,然后通过triggerEffects触发这deps中的副作用函数执行(通过effect.run或effect.scheduler)
has(触发自in操作): 如果不是Symbol值,就以HAS类型执行track收集依赖,并返回has操作的结果
deleteProperty(触发自delete操作): 如果target有相应的key值,并且删除成功,就以DELETE类型执行trigger触发依赖,key为关联,并将删除的老值传过去,并返回deleteProperty操作的结果
ownKeys(触发自for-in操作): 以ITERATE类型执行track收集依赖,如果操作目标是数组,则使用length属性作为key建立关联,如果不是数组是对象,则使用ITERATE_KEY建立关联
接下来我们回到effect.run的执行
此时是triggerEffects触发依赖通过effect.run执行相对应的副作用函数这一步
effect.run方法 路径: core\packages\reactivity\src\effect.ts
// From triggerEffects:
run() {
// 如果当前不是活跃的
if (!this.active) {
// 返回fn fn是effect函数传入的参数,通常是更新函数也就是副作用函数
return this.fn()
}
//todo parent的操作涉及effectScrop
let parent: ReactiveEffect | undefined = activeEffect
let lastShouldTrack = shouldTrack
while (parent) {
if (parent === this) {
return
}
parent = parent.parent
}
try {
// 取得effect
this.parent = activeEffect
// 将this赋给activeEffect
activeEffect = this
// 将shouldTrack置为true
shouldTrack = true
//todo 涉及响应式性能优化
trackOpBit = 1 << ++effectTrackDepth
// 响应式性能优化的地方,先看else
if (effectTrackDepth <= maxMarkerBits) {
initDepMarkers(this)
} else {
// 执行cleanupEffect
cleanupEffect(this)
}
// 返回fn fn是effect函数传入的参数,通常是更新函数也就是副作用函数
return this.fn()
} finally {
// 响应式性能优化
if (effectTrackDepth <= maxMarkerBits) {
finalizeDepMarkers(this)
}
trackOpBit = 1 << --effectTrackDepth
// 涉及effectScope
activeEffect = this.parent
shouldTrack = lastShouldTrack
this.parent = undefined
}
}
run方法做了什么?
这一步的注释在: effect.run
可以看到在执行fn副作用函数之前,先执行了cleanupEffects方法清除依赖,但为什么要清除依赖呢?
举个例子:
例子来自《Vuejs设计与实现》 p50
effect(function effectFn() {
document.body.innerText = obj.ok ? obj.text : 'not'
})
当obj.ok的值为true时,会收集obj.ok和obj.text的依赖,当改变obj.ok为false后,我们期望的是,去改变obj.text值时,obj.text收集的依赖的副作用函数不会去执行。
但是如果没有清除依赖这一步的话,当我们将obj.ok设为false后,对obj.text的依赖还存在,那么当我们修改obj.text的值时,obj.text依赖的副作用函数还是会执行,这不是我们所期望的
因此每次副作用函数执行前,都会执行一次清除依赖。而在副作用函数执行时,会重新建立依赖
那么来看cleanupEffects的实现
cleanupEffects方法 路径:
// From effect.run:
// Return To run: 清空deps中所有依赖
function cleanupEffect(effect: ReactiveEffect) {
// 解构出deps数组,trackEffects中activeEffect!.deps.push(dep)这一步就是为了这里
const { deps } = effect
// 如果deps不为空
if (deps.length) {
// 遍历deps
for (let i = 0; i < deps.length; i++) {
// 将每一个dep中的effect依赖(副作用函数)移除
deps[i].delete(effect)
}
// 长度置为0
deps.length = 0
}
}
cleanupEffects方法做了什么?
这一步就是清空deps中所有的依赖
这一步的注释在: cleanupEffect
知道了这个清除依赖之后,是时候来看看响应式优化相关的处理了,这个优化就是对清除依赖进行的优化
我们来看看之前留下的响应式优化相关吧
在Vue3.2.0之前,每次副作用函数执行前,都会触发cleanupEffect清空deps中所有依赖,然后在副作用函数执行过程中重新收集依赖,此过程中涉及到大量的对set的增删操作
来看看是怎么进行优化的吧
createDep方法 路径: core\packages\reactivity\src\dep.ts
// From track:
// Return To track: 创建一个set集合,并且set集合有两个关于响应式性能优化的属性w和n
export const createDep = (effects?: ReactiveEffect[]): Dep => {
//todo: 创建一个新的set集合dep,并添加两个属性w,n (涉及到响应式性能优化,会细说)
const dep = new Set<ReactiveEffect>(effects) as Dep
// w表示是否已经被收集
dep.w = 0
// n表示是否是新收集的
dep.n = 0
return dep
}
回到createDep方法中,创建dep时,初始化了两个属性:其中 w 表示是否已经被收集(可以这么理解: 老依赖),n 表示是否新收集(新依赖)。
三个全局变量 路径: core\packages\reactivity\src\effect.ts
// The number of effects currently being tracked recursively.
// 当前正在递归跟踪的effects数。
let effectTrackDepth = 0
// 递归嵌套层数
export let trackOpBit = 1
/**
* The bitwise track markers support at most 30 levels of recursion.
* This value is chosen to enable modern JS engines to use a SMI on all platforms.
* When recursion depth is greater, fall back to using a full cleanup.
* 按位跟踪标记最多支持 30 级递归。
* 选择此值是为了使现代 JS 引擎能够在所有平台上使用 SMI。
* 当递归深度更大时,回退到使用完全清理。
*/
const maxMarkerBits = 30
effectTrackDepth: 当前正在递归跟踪的effects数
trackOpBit: 递归嵌套层数
maxMarkerBits: 最大嵌套层数
现在继续回到effect.run中
effect.run 路径: core\packages\reactivity\src\effect.ts
try {
// 取得effect
this.parent = activeEffect
// 将this赋给activeEffect
activeEffect = this
// 将shouldTrack置为true
shouldTrack = true
// 使用左移运算符来表示递归的层数
// effectTrackDepth初始为0 ++之后为1 1 << 1 = 2
// 位运算如下: 0000001 左移一位 0000010 此时代表调用了一次effect 0000100 代表递归嵌套了两层
// 使用位运算可以处理嵌套的effect
trackOpBit = 1 << ++effectTrackDepth
// 如果effectTrackDepth <= 30 调用initDepMarkers,否则降级到cleanupEffect
if (effectTrackDepth <= maxMarkerBits) {
initDepMarkers(this)
} else {
// 执行cleanupEffect
// From: run
// To: cleanupEffect
// Return From cleanupEffect: 清空依赖
cleanupEffect(this)
}
// 返回fn fn是effect函数传入的参数,通常是更新函数也就是副作用函数
return this.fn()
} finally {
if (effectTrackDepth <= maxMarkerBits) {
finalizeDepMarkers(this)
}
trackOpBit = 1 << --effectTrackDepth
可以看到中间使用了左移位运算,那么这操作是为了什么?
使用trackOpBit来代表递归嵌套的层数,通过左移,可以知道现在嵌套了几层,这样就能处理嵌套的effect
当effectTrackDepth小于等于30时,就调用initDepMarkers而不是cleanupEffects了
initDepMarkers方法 路径: core\packages\reactivity\src\dep.ts
// From effect.run:
// Return To effect.run: 初始化deps的w属性,代表已经收集依赖
export const initDepMarkers = ({ deps }: ReactiveEffect) => {
// 如果deps不为空
if (deps.length) {
// 遍历dpes
for (let i = 0; i < deps.length; i++) {
// 给每个dep的w属性与trackOpBit(记录递归嵌套的层数)进行 | 运算,代表相对应层次的已经收集依赖
deps[i].w |= trackOpBit // set was tracked
}
}
}
initDepMarkers方法做了什么?
主要就是将现在deps的依赖全部用w属性表示,代表收集了的老依赖
执行完initDepMarkers,不要忘了还有最重要的fn副作用函数要执行
fn执行,会执行到trackEffects(这一步为什么会执行到这里,之后会在何时触发依赖收集里面说到,现在只要先知道会触发就行)
现在回到trackEffects之前留下的todo
trackEffects方法 路径: core\packages\reactivity\src\effect.ts
// From track:
// Return To track: 将activeEffect添加到dep中,并且将dep添加到activeEffect.deps中,deps就是一个与当前副作用函数存在联系的依赖集合,为了在清理时能够知道是否需要清理
export function trackEffects(
dep: Dep,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
// 将shouldTrack置为false
let shouldTrack = false
// 如果effectTrackDepth <= 30 没有超过最大递归层数
if (effectTrackDepth <= maxMarkerBits) {
// From trackEffects:
// To newTracked:
// Retrun From newTracked: 判断是否是新的依赖
// 如果不是新的依赖
if (!newTracked(dep)) {
// 设置为新的依赖
dep.n |= trackOpBit // set newly tracked
// From trackEffects:
// To wasTracked:
// Reutrn from wasTracked: 看dep.w和递归层数是否相同,如果相同,则说明dep.w已经被收集,返回true,如果不同,返回false
// 如果已经收集了,shouldTrack为false,如果没有收集shuoldTrack为true
shouldTrack = !wasTracked(dep)
}
} else {
// Full cleanup mode.
// 完全清理模式。
// 如果Dep有activeEffect,shouldTrack为false,代表不用收集,如果没有,则需要收集
shouldTrack = !dep.has(activeEffect!)
}
}
trackEffects方法做了什么?
使用dep.n属性表示新收集的依赖
现在回到effct.run执行的finally这里
effct.run finally 路径: core\packages\reactivity\src\effect.ts
finally {
// 如果effectTrackDepth <= 30 调用finalizeDepMarkers
if (effectTrackDepth <= maxMarkerBits) {
finalizeDepMarkers(this)
}
// 递归层数恢复到上一级
trackOpBit = 1 << --effectTrackDepth
finally里面做了什么?
finalizeDepMarkers方法 路径: core\packages\reactivity\src\dep.ts
// From effect.run:
// Return To effect.run: 遍历deps,删除曾经被收集过但不是新的依赖,将新的依赖添加到deps中,最后得到的就是删除不必要的旧依赖后的deps
export const finalizeDepMarkers = (effect: ReactiveEffect) => {
// 解构出effect中的deps
const { deps } = effect
// 如果deps不为空
if (deps.length) {
// 索引
let ptr = 0
// 遍历deps
for (let i = 0; i < deps.length; i++) {
// 取得dep
const dep = deps[i]
// 曾经被收集过但不是新的依赖,需要删除
if (wasTracked(dep) && !newTracked(dep)) {
// 删除依赖
dep.delete(effect)
} else {
// 如果是新收集的依赖,则放入deps中
deps[ptr++] = dep
}
// clear bits
// 清空状态
dep.w &= ~trackOpBit
dep.n &= ~trackOpBit
}
// deps数组长度等于删除不必要依赖之后的长度
deps.length = ptr
}
}
finalizeDepMarkers方法做了什么?
其实就是将之前initDepMarkers中收集的w老依赖和trackEffects中收集的n新依赖进行对比,如果老依赖在新依赖里面没有,就需要删除,如果有就不需要删除,最后得到删除了不需要的老依赖的依赖数组
cleanupEffect会删除deps中所有的依赖,而优化后只需要删除不需要的老依赖,减少了对set的增删,因此就得到了相对应的优化
这一步的注释在: 响应式优化
这就是reactive的一些操作了,其实看起来挺简单的是吧
看完了reactive,那我们就接着把其他三个一起看看吧
之前我们看的是mutableHandlers
现在在baseHandlers中还剩下:
这三个对象分别对应三个响应式方法:
shallowReactiveHandlers: shallowReactive
readonlyHandlers: readonly
shallowReadonlyHandlers: shallowReadonlyHandlers
shallowReactiveHandlers对象 路径: core-main\packages\reactivity\src\baseHandlers.ts
// From createReactiveObject:
// To extend:
// Return From extend: extend就是Object.assign方法
// Return To createReactiveObject: 返回如下方法
export const shallowReactiveHandlers = /*#__PURE__*/ extend(
{},
mutableHandlers,
{
get: shallowGet,
set: shallowSet
}
)
extend就是Object.assign
因此shallowReactiveHandlers对象是这样的
const shallowReactiveHandlers = {
get: shallowGet,
set: shallowSet,
deleteProperty,
has,
ownKeys
}
可以看到deleteProperty、has、ownKeys方法和reactive的是一样的
get和set则是shallowGet和shallowSet
shallowGet 路径: core\packages\reactivity\src\baseHandlers.ts
const shallowGet = /*#__PURE__*/ createGetter(false, true)
// 传入参数后 shallow为true
function createGetter(isReadonly = false, shallow = false)
可以看到createGetter此次的shallow为true
这里的createGetter代码就不放上来了,可以自己去看一下,当shallow为true时,只会执行一次最表面的track,不会去判断是否是对象从而递归
因此shallowGet只会收集一次表面的依赖
shallowSet 路径: core\packages\reactivity\src\baseHandlers.ts
const shallowSet = /*#__PURE__*/ createSetter(true)
// 传入参数后 shallow为true
function createSetter(shallow = false)
可以看到createSetter此次的shallow为true
看看createSetter内部怎么处理的
// 如果shallow是false,并且新值不是只读
if (!shallow && !isReadonly(value)) {
} else {
// in shallow mode, objects are set as-is regardless of reactive or not
// 在shallow模式下,无论是否是reactive,对象都按原样设置
}
之后的操作都还是一样的,只是shallow模式下面,对象都按原样设置
readonlyHandlers对象 路径: core-main\packages\reactivity\src\baseHandlers.ts
// From createReactiveObject:
// Return To createReactiveObject: 返回如下方法
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
}
}
可以看到readonlyHandlers只有三个方法: get、set、deleteProperty
不过set和deleteProperty都只是返回true,不会执行其他操作
因此来看get
const readonlyGet = /*#__PURE__*/ createGetter(true)
// 传入参数后 isReadonly为true
function createGetter(isReadonly = false, shallow = false)
将isReadonly为true代入进createGetter中
// 如果是只读,不会进行数组方法的重写
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
// 如果是只读,不会进行track收集依赖
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
// 如果是对象,递归遍历执行readonly
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res)
}
可以看到isReadonly为true时,并不会收集依赖,不会进行数组方法的重写,如果是对象的话,还会递归执行readonly,都不会收集依赖
所以readonly不会收集依赖,只能读取值
shallowReadonlyHandlers对象 路径: core-main\packages\reactivity\src\baseHandlers.ts
// From createReactiveObject:
// Return To createReactiveObject: 返回如下方法
export const shallowReadonlyHandlers = /*#__PURE__*/ extend(
{},
readonlyHandlers,
{
get: shallowReadonlyGet
}
)
经过extends之后的shallowReadonlyHandlers对象
const shallowReadonlyHandlers = {
get: shallowReadonlyGet,
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
}
}
可以看到shallowReadonlyHandlers只有三个方法: get、set、deleteProperty
不过set和deleteProperty都只是返回true,不会执行其他操作
因此来看get
const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true)
// 传入参数后 isReadonly为true shallow为true
function createGetter(isReadonly = false, shallow = false)
将isReadonly为true shallow为true代入进createGetter中
// 如果是只读,不会进行数组方法的重写
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
// 如果是只读,不会进行track收集依赖
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
// 如果是shallow浅响应式,返回没有经过依赖收集的res
if (shallow) {
return res
}
可以看到,如果isReadonly为true、shallow为true,不会进行数组方法的重写,不会进行track收集依赖,并且在shallow这里返回后,不会进行对象的递归
所以shallowReadonly不会进行数组方法的重写,不会进行track收集依赖,并且不会进行对象的递归,形成浅只读
集合类型里面涉及的方法较多,也不是很常用,所以我这里放了代码逐行注释,想了解的可以看一看,觉得繁琐的可以跳到下一段
现在分析完了baseHandlers,其实这只是一部分,让我们回到createReactiveObject方法中
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
可以看到,我们之前分析的是baseHandlers,现在如果是集合类型,就会走collectionHandlers
现在就来到collectionHandlers,而collectionHandlers其实是传进来的mutableCollectionHandlers
mutableCollectionHandlers 路径: core\packages\reactivity\src\collectionHandlers.ts
// From createReactiveObject
export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {
// get方法来自createInstrumentationGetter方法
get: /*#__PURE__*/ createInstrumentationGetter(false, false)
}
可以看到mutableCollectionHandlers是一个包含get方法的对象,这个get方法来自createInstrumentationGetter方法
createInstrumentationGetter方法 路径: core\packages\reactivity\src\collectionHandlers.ts
// From mutableCollectionHandlers get:
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
// 经过一系列判断获得instrumentations对象
const instrumentations = shallow
? isReadonly
? shallowReadonlyInstrumentations
: shallowInstrumentations
: isReadonly
? readonlyInstrumentations
: mutableInstrumentations
// 返回一个函数,这个函数就是get
return (
target: CollectionTypes,
key: string | symbol,
receiver: CollectionTypes
) => {
// 如果key是ReactiveFlags.IS_REACTIVE
if (key === ReactiveFlags.IS_REACTIVE) {
// 返回!isReadonly
return !isReadonly
// 如果key是ReactiveFlags.IS_READONLY
} else if (key === ReactiveFlags.IS_READONLY) {
// 返回isReadonly
return isReadonly
// 如果key是ReactiveFlags.RAW
} else if (key === ReactiveFlags.RAW) {
// 返回target
return target
}
// 返回Reflect.get操作结果
return Reflect.get(
// 如果instrumentations对象是否有key 并且key在target上,返回instrumentations[key],否则返回target[key]
hasOwn(instrumentations, key) && key in target
? instrumentations
: target,
key,
receiver
)
}
}
createInstrumentationGetter方法做了什么?
instrumentations对象 路径: core\packages\reactivity\src\collectionHandlers.ts
// From createInstrumentationGetter:
const [
mutableInstrumentations,
readonlyInstrumentations,
shallowInstrumentations,
shallowReadonlyInstrumentations
] = /* #__PURE__*/ createInstrumentations()
可以看到这几个对象都来自createInstrumentations方法
createInstrumentations方法 路径: core\packages\reactivity\src\collectionHandlers.ts
// From createInstrumentationGetter:
function createInstrumentations() {
// mutableInstrumentations是键类型为string,值类型为Function的对象
const mutableInstrumentations: Record<string, Function> = {
// get方法
get(this: MapTypes, key: unknown) {
return get(this, key)
},
// size的getter
get size() {
return size(this as unknown as IterableCollections)
},
// has
has,
// add
add,
// set
set,
// delte
delete: deleteEntry,
// clear
clear,
// forEach
forEach: createForEach(false, false)
}
// shallowInstrumentations对象
const shallowInstrumentations: Record<string, Function> = {
get(this: MapTypes, key: unknown) {
return get(this, key, false, true)
},
get size() {
return size(this as unknown as IterableCollections)
},
has,
add,
set,
delete: deleteEntry,
clear,
forEach: createForEach(false, true)
}
// readonlyInstrumentations对象
const readonlyInstrumentations: Record<string, Function> = {
get(this: MapTypes, key: unknown) {
return get(this, key, true)
},
get size() {
return size(this as unknown as IterableCollections, true)
},
has(this: MapTypes, key: unknown) {
return has.call(this, key, true)
},
add: createReadonlyMethod(TriggerOpTypes.ADD),
set: createReadonlyMethod(TriggerOpTypes.SET),
delete: createReadonlyMethod(TriggerOpTypes.DELETE),
clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
forEach: createForEach(true, false)
}
// shallowReadonlyInstrumentations对象
const shallowReadonlyInstrumentations: Record<string, Function> = {
get(this: MapTypes, key: unknown) {
return get(this, key, true, true)
},
get size() {
return size(this as unknown as IterableCollections, true)
},
has(this: MapTypes, key: unknown) {
return has.call(this, key, true)
},
add: createReadonlyMethod(TriggerOpTypes.ADD),
set: createReadonlyMethod(TriggerOpTypes.SET),
delete: createReadonlyMethod(TriggerOpTypes.DELETE),
clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
forEach: createForEach(true, true)
}
// 通过createIterableMethod方法操作keys values entries Symbol.iterator迭代器方法
const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator]
iteratorMethods.forEach(method => {
mutableInstrumentations[method as string] = createIterableMethod(
method,
false,
false
)
readonlyInstrumentations[method as string] = createIterableMethod(
method,
true,
false
)
shallowInstrumentations[method as string] = createIterableMethod(
method,
false,
true
)
shallowReadonlyInstrumentations[method as string] = createIterableMethod(
method,
true,
true
)
})
// 返回这四个对象
return [
mutableInstrumentations,
readonlyInstrumentations,
shallowInstrumentations,
shallowReadonlyInstrumentations
]
}
createInstrumentations方法做了什么?
现在来看mutableInstrumentations内部的方法
get 路径: core\packages\reactivity\src\collectionHandlers.ts
// From mutableInstrumentations:
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]
// 获取原始target
const rawTarget = toRaw(target)
// 获取原始key
const rawKey = toRaw(key)
// 如果key不等于原始key
if (key !== rawKey) {
// 如果不是只读,以GET类型执行track,以key为关联
!isReadonly && track(rawTarget, TrackOpTypes.GET, key)
}
// 以GET类型执行track,以key为关联,以rawKey为关联
!isReadonly && track(rawTarget, TrackOpTypes.GET, rawKey)
// From get:
// To getProto:
// Return From getProto: 返回对象原型
// 从rawTarget获取原型,并从原型中解构出has方法
const { has } = getProto(rawTarget)
// From get:
// To toShallow:
// Return From toShallow: 啥也没做,返回value值
// To toReadOnly:
// Return From toReadOnly: 如果是对象,返回readonly(value),如果不是对象,返回value
// To toReactive:
// Return From toReactive: 如果是对象,返回reactive(value),如果不是对象,返回value
const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
// 如果rawTraget上有key
if (has.call(rawTarget, key)) {
// 这里wrap是包裹一层响应式数据,避免数据污染
// 返回包裹响应式的target.get(key)
return wrap(target.get(key))
// 如果rawTraget上有rawKey
} else if (has.call(rawTarget, rawKey)) {
// 这里wrap是包裹一层响应式数据,避免数据污染
// 返回包裹响应式的target.get(rawKey)
return wrap(target.get(rawKey))
// 如果target不等于rawTarget 不等于原始target
} else if (target !== rawTarget) {
// #3602 readonly(reactive(Map))
// ensure that the nested reactive `Map` can do tracking for itself
// 确保嵌套的响应式 `Map` 可以自己进行跟踪
target.get(key)
}
}
get做了什么?
size 路径: core\packages\reactivity\src\collectionHandlers.ts
// From mutableInstrumentations:
// Return To mutableInstrumentations: 如果不是只读,以TrackOpTypes.ITERATE类型执行track,以ITERATE_KEY为关联 因为改变size会影响for in循环,因此用ITERATE_KEY,并返回Reflect.get size的返回值
function size(target: IterableCollections, isReadonly = false) {
// 获取原始target
target = (target as any)[ReactiveFlags.RAW]
// 如果不是只读 以TrackOpTypes.ITERATE类型执行track,以ITERATE_KEY为关联 因为改变size会影响for in循环,因此用ITERATE_KEY
!isReadonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
// 返回Reflect.get size的返回值
return Reflect.get(target, 'size', target)
}
size做了什么?
has 路径: core\packages\reactivity\src\collectionHandlers.ts
// From mutableInstrumentations:
// Return To has: 根据key是否等于原始key,以GET类型执行track,以key或rawKey作为关联,判断key和rawKey是否相等,相等返回target.has(key)的结果,不相等返回target.has(key) || target.has(rawKey)的结果
function has(this: CollectionTypes, key: unknown, isReadonly = false): boolean {
// 获取原始target
const target = (this as any)[ReactiveFlags.RAW]
// 原始traget
const rawTarget = toRaw(target)
// 原始key
const rawKey = toRaw(key)
// 如果key不等于原始key
if (key !== rawKey) {
// 如果不是只读,以GET类型执行track,以key为关联
!isReadonly && track(rawTarget, TrackOpTypes.HAS, key)
}
// 以GET类型执行track,以key为关联,以rawKey为关联
!isReadonly && track(rawTarget, TrackOpTypes.HAS, rawKey)
// 判断是否相等,相等返回target.has(key)的结果,不相等返回target.has(key) || target.has(rawKey)的结果
return key === rawKey
? target.has(key)
: target.has(key) || target.has(rawKey)
}
has做了什么?
add 路径: core\packages\reactivity\src\collectionHandlers.ts
// From mutableInstrumentations:
// Return To mutableInstrumentations: 如果value不在target上,执行target.add添加,以ADD类型执行trigger,以value为关联,将value值传过去,返回添加后的集合
function add(this: SetTypes, value: unknown) {
// 原始value
value = toRaw(value)
// 原始集合
const target = toRaw(this)
// 获取target的原型对象
const proto = getProto(target)
// 调用原型对象的has方法,判断value是否在target中,在的话为true,不在为fasle
const hadKey = proto.has.call(target, value)
// 判断hadKey
if (!hadKey) {
// 如果hadKey为false,执行target.add方法
target.add(value)
// 以ADD类型执行trigger,以value为关联,将value值传过去
trigger(target, TriggerOpTypes.ADD, value, value)
}
// 返回添加后的集合
return this
}
add做了什么?
set 路径: core\packages\reactivity\src\collectionHandlers.ts
// From mutableInstrumentations:
// Return To set: 判断key是否在target上,如果不在,用原始key去判断,之后执行set方法,
// 在判断key是否在target上,如果在,并且新旧值发生改变,以SET类型执行trigger,以key为关联,将新旧值都传过去,
// 如果不在,以ADD类型执行trigger,以key为关联,将value值传过去
function set(this: MapTypes, key: unknown, value: unknown) {
// 获取原始value
value = toRaw(value)
// 获取原始集合
const target = toRaw(this)
// getProto获取target的原型对象,并从中解构出has和get方法
const { has, get } = getProto(target)
// hadKey target是否有key,如果有为true,否则为false
let hadKey = has.call(target, key)
// 判断hadKey
if (!hadKey) {
// 如果hadKey为false,获取原始key
key = toRaw(key)
// 再用原始key去判断hadKey
hadKey = has.call(target, key)
} else if (__DEV__) {
// DEV忽略
checkIdentityKeys(target, has, key)
}
// 获取oldValue
const oldValue = get.call(target, key)
// 调用set方法
target.set(key, value)
// 再判断hadKey
if (!hadKey) {
// 如果为false,以ADD类型执行trigger,以key为关联,将value值传过去
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
// 如果hadKey为true,并且新旧值发生改变,以SET类型执行trigger,以key为关联,将新旧值都传过去
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
// 返回执行后的集合
return this
}
set做了什么?
delete 路径: core\packages\reactivity\src\collectionHandlers.ts
// From mutableInstrumentations:
// Return To mutableInstrumentations: 判断key是否在target上,如果不在,用原始key去判断,之后执行delete方法
// 如果key在target上,以DELETE类型执行trigger,以key为关联,将undefined,oldValue传过去
// 返回delete执行的结果
function deleteEntry(this: CollectionTypes, key: unknown) {
// 获取原始集合
const target = toRaw(this)
// 解构出原型对象上的has和get方法
const { has, get } = getProto(target)
// 判断key是否在target上,如果在,hadKey为true,如果不在hadKey为false
let hadKey = has.call(target, key)
// 判断hadKey
if (!hadKey) {
// 如果hadKey为false,获取原始key
key = toRaw(key)
// 用原始key再去判断hadKey
hadKey = has.call(target, key)
} else if (__DEV__) {
// DEV忽略
checkIdentityKeys(target, has, key)
}
// 获取oldValue
const oldValue = get ? get.call(target, key) : undefined
// forward the operation before queueing reactions
// 在queue reactions之前调用delete方法
const result = target.delete(key)
// 判断hadKey
if (hadKey) {
// 如果key在target上,以DELETE类型执行trigger,以key为关联,将undefined,oldValue传过去
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
}
// 返回delete执行的结果
return result
}
delete做了什么?
clear 路径: core\packages\reactivity\src\collectionHandlers.ts
// From mutableInstrumentations:
// Return To mutableInstrumentations: 执行clear,判断hadItems,如果为true,以CLEAR类型执行trigger,以undefined为关联,将undefined,oldTarget传过去,返回clear执行的结果
function clear(this: IterableCollections) {
// 获取原始集合
const target = toRaw(this)
// 判断是否有值 size !== 0 为true size === 0 为fasle
const hadItems = target.size !== 0
const oldTarget = __DEV__
? // DEV忽略
isMap(target)
? new Map(target)
: new Set(target)
: undefined
// forward the operation before queueing reactions
// 在queue reactions之前调用clear方法
const result = target.clear()
// 判断hadItems
if (hadItems) {
// 如果hadItems为true,以CLEAR类型执行trigger,以undefined为关联,将undefined,oldTarget传过去
trigger(target, TriggerOpTypes.CLEAR, undefined, undefined, oldTarget)
}
// 返回clear执行的结果
return result
}
clear做了什么?
forEach 路径: core\packages\reactivity\src\collectionHandlers.ts
// From mutableInstrumentations:
// Return To mutableInstrumentations: 如果不是只读 以ITERATE类型执行track,以ITERATE_KEY为关联,返回forEach执行的结果
function createForEach(isReadonly: boolean, isShallow: boolean) {
return function forEach(
this: IterableCollections,
callback: Function,
thisArg?: unknown
) {
// 获取集合
const observed = this as any
// 获取原始集合
const target = observed[ReactiveFlags.RAW]
// 获取原始target
const rawTarget = toRaw(target)
// wrap包裹响应式
const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
// 如果不是只读 以ITERATE类型执行track,以ITERATE_KEY为关联
!isReadonly && track(rawTarget, TrackOpTypes.ITERATE, ITERATE_KEY)
// 返回forEach执行的结果
return target.forEach((value: unknown, key: unknown) => {
// important: make sure the callback is
// 1. invoked with the reactive map as `this` and 3rd arg
// 2. the value received should be a corresponding reactive/readonly.
// 重要:确保回调是
// 1. 使用反应映射作为 `this` 和 3rd arg 调用
// 2. 收到的值应该是相应的反应/只读。
return callback.call(thisArg, wrap(value), wrap(key), observed)
})
}
}
forEach做了什么?
createIterableMethod方法 路径: core\packages\reactivity\src\collectionHandlers.ts
// From createInstrumentations:
// Return To createInstrumentations: 如果不是只读,以ITERATE类型执行track,根据isKeyOnly决定以ITERATE_KEY或MAP_KEY_ITERATE_KEY为关联,返回一个包装的迭代器,它返回观察到的版本,实现可迭代协议
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)
// From createIterableMethod:
// Return From createIterableMethod: 判断是否是map类型
const targetIsMap = isMap(rawTarget)
// 如果是entries或是Sybol.iterator方法并且是map类型 为true
const isPair =
method === 'entries' || (method === Symbol.iterator && targetIsMap)
const isKeyOnly = method === 'keys' && targetIsMap
// 获取原始迭代器方法
const innerIterator = target[method](...args)
// wrap包裹函数
const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
// 如果不是只读,以ITERATE类型执行track,根据isKeyOnly决定以ITERATE_KEY或MAP_KEY_ITERATE_KEY为关联
!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
// 返回一个包装的迭代器,它返回观察到的版本
// 从实际迭代器发出的值
return {
// iterator protocol
next() {
// 调用原始迭代器的next方法获取value和done
const { value, done } = innerIterator.next()
return done
// 如果done存在返回value和done
? { value, done }
: { // 如果done不存在,value根据isPair决定返回什么 使用wrap包裹
value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value),
done
}
},
// iterable protocol
// 实现可迭代协议
[Symbol.iterator]() {
return this
}
}
}
}
createIterableMethod做了什么?
集合类型还有其他三个,和之前看的那些handlers对象其实差不多,就是在我们分析的这些方法上通过传入shallow,readonly参数改变方法的执行结果,我就不多去看了,大家可以自己结合上面的代码去分析分析
现在来看ref
ref方法 路径: core\packages\reactivity\src\ref.ts
// ref
export function ref<T extends object>(
value: T
): [T] extends [Ref] ? T : Ref<UnwrapRef<T>>
export function ref<T>(value: T): Ref<UnwrapRef<T>>
export function ref<T = any>(): Ref<T | undefined>
export function ref(value?: unknown) {
return createRef(value, false)
}
ref方法做了什么?
可以看到ref其实内部是返回的createRef方法的调用返回值
createRef方法 路径: core\packages\reactivity\src\ref.ts
// From ref:
function createRef(rawValue: unknown, shallow: boolean) {
// 判断是否是ref
if (isRef(rawValue)) {
// 如果是ref,直接返回
return rawValue
}
// 返回一个RefImpl对象
return new RefImpl(rawValue, shallow)
}
createRef方法做了什么?
class RefImpl 路径:core\packages\reactivity\src\ref.ts
// From createRef:
class RefImpl<T> {
private _value: T
private _rawValue: T
public dep?: Dep = undefined
public readonly __v_isRef = true
constructor(value: T, public readonly __v_isShallow: boolean) {
// 根据是否是shallow赋值
// 如果不是shallow rawValue就是原始的value
this._rawValue = __v_isShallow ? value : toRaw(value)
// From RefImpl:
// To: toReactive
// Retrun From toReactive: 如果是对象,返回reactive(value),如果不是对象,返回value
// 如果不是shaollw 如果value是对象,_value是toReactive(value),如果不是对象,_value是value
this._value = __v_isShallow ? value : toReactive(value)
}
// 对value的getter
get value() {
// 执行trackRefValue
trackRefValue(this)
// 返回_value
return this._value
}
// 对value的setter
set value(newVal) {
// 如果不是shallow newVal就是newVal的原始值
newVal = this.__v_isShallow ? newVal : toRaw(newVal)
// 如果新、旧值发生改变
if (hasChanged(newVal, this._rawValue)) {
// 赋值
this._rawValue = newVal
// 如果不是shaollw 如果value是对象,_value是toReactive(newVal),如果不是对象,_value是newVal
this._value = this.__v_isShallow ? newVal : toReactive(newVal)
// 执行triggerRefValue
triggerRefValue(this, newVal)
}
}
}
class RefImpl做了什么?
这一步的注释在: class RefImpl
触发get时会触发trackRefValue
触发set时会触发triggerRefValue
因此来看这两个方法
trackRefValue方法 路径: core\packages\reactivity\src\ref.ts
// From RefImpl:
// Return To RefImpl: 调用trackEffects收集依赖
export function trackRefValue(ref: RefBase<any>) {
// 判断shouldTrack和activeEffect
if (shouldTrack && activeEffect) {
// 如果shouldTrack为true,并且activeEffect不为null,使用toRaw获取原始ref
ref = toRaw(ref)
// DEV忽略
if (__DEV__) {
trackEffects(ref.dep || (ref.dep = createDep()), {
target: ref,
type: TrackOpTypes.GET,
key: 'value'
})
} else {
// 调用trackEffects方法收集依赖,这个依赖是收集到dep上的
trackEffects(ref.dep || (ref.dep = createDep()))
}
}
}
trackRefValue方法做了什么?
trackRefValue也是调用了trackEffects进行依赖收集
triggerValue方法 路径: core\packages\reactivity\src\ref.ts
// From RefImpl:
// Return To RefImpl: 调用triggerEffects触发依赖,执行副作用函数
export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
// toRaw获取原始ref
ref = toRaw(ref)
// 如果ref.dep存在
if (ref.dep) {
// DEV忽略
if (__DEV__) {
triggerEffects(ref.dep, {
target: ref,
type: TriggerOpTypes.SET,
key: 'value',
newValue: newVal
})
} else {
// 调用triggerEffects触发依赖,执行副作用函数
triggerEffects(ref.dep)
}
}
}
triggerValue方法做了什么?
triggerValue也是调用triggerEffects触发依赖
现在来看computed
computed方法 路径: core\packages\reactivity\src\computed.ts
// computed
export function computed<T>(
getter: ComputedGetter<T>,
debugOptions?: DebuggerOptions
): ComputedRef<T>
export function computed<T>(
options: WritableComputedOptions<T>,
debugOptions?: DebuggerOptions
): WritableComputedRef<T>
export function computed<T>(
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
debugOptions?: DebuggerOptions,
isSSR = false
) {
// getter
let getter: ComputedGetter<T>
// setter
let setter: ComputedSetter<T>
// From computed:
// To isFunction:
// Return From isFunction: 判断是否是function类型
// 判断getterOrOptions是否是函数,如果是函数就是get,如果不是就是get和set
const onlyGetter = isFunction(getterOrOptions)
if (onlyGetter) {
// 如果是函数,就将get函数赋值给getter
getter = getterOrOptions
// setter不用
setter = __DEV__
? () => {
console.warn('Write operation failed: computed value is readonly')
}
: NOOP
} else {
// 如果不是函数
// 将对象的get赋给getter
getter = getterOrOptions.get
// 将对象的set赋给setter
setter = getterOrOptions.set
}
// 创建一个新的computedRefImpl对象
const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR)
// DEV忽略
if (__DEV__ && debugOptions && !isSSR) {
cRef.effect.onTrack = debugOptions.onTrack
cRef.effect.onTrigger = debugOptions.onTrigger
}
// 返回cRef对象
return cRef as any
}
computed方法做了什么?
因此来看class computedRefImpl
class computedRefImpl 路径: core\packages\reactivity\src\computed.ts
// From computed:
export class ComputedRefImpl<T> {
public dep?: Dep = undefined
private _value!: T
public readonly effect: ReactiveEffect<T>
public readonly __v_isRef = true
public readonly [ReactiveFlags.IS_READONLY]: boolean
public _dirty = true
public _cacheable: boolean
constructor(
getter: ComputedGetter<T>,
private readonly _setter: ComputedSetter<T>,
isReadonly: boolean,
isSSR: boolean
) {
// 通过new ReactiveEffect创建effect副作用函数,第二个参数是scheduler
this.effect = new ReactiveEffect(getter, () => {
// 当值发生变化时,判断dirty,使用调度器将dirty重置为true
if (!this._dirty) {
// 如果dirty为false,则设置dirty为true
this._dirty = true
// 手动调用triggerRefValue 避免嵌套的effect
triggerRefValue(this)
}
})
// 给effect设置computed
this.effect.computed = this
// 给effect设置active 如果是ssr则为false,如果不是就是true
this.effect.active = this._cacheable = !isSSR
this[ReactiveFlags.IS_READONLY] = isReadonly
}
// value的getter
get value() {
// the computed ref may get wrapped by other proxies e.g. readonly() #3376
// 计算的 ref 可能会被其他代理包裹,例如 只读()
// 通过toRaw获取原始的值
const self = toRaw(this)
// 手动调用trackRefValue,去收集依赖 避免嵌套的effect
trackRefValue(self)
// 判断dirty和_cacheable 只有脏了才计算值,
if (self._dirty || !self._cacheable) {
// 如果dirty为true或者_cacheable为false 代表脏了
// 则设置dirty为false
self._dirty = false
// 执行self.effect.run()
self._value = self.effect.run()!
}
// 返回value
return self._value
}
// value的setter
set value(newValue: T) {
this._setter(newValue)
}
}
class computedRefImpl做了什么?
关于响应式模块的分析已经差不多结束了,现在消化一些问题吧!
首先,要触发响应式,肯定要收集依赖,才能触发依赖执行副作用函数,那么首次的依赖收集是什么时候发生的呢?不可能等到我们手动调用才会去收集依赖吧
在我的Vue3组件初始化流程文章中说过调用update方法执行componentUpdateFn更新函数实现首次视图更新
componentUpdateFn更新函数内部会去执行template通过compile(编译器 之后文章会说) 生成的render方法,而render方法会读取属性,执行get操作,因此会进行收集依赖,所以首次依赖收集是在mountComponent中的setupRenderEffect方法中的update中发生的。
结合我的mini-vue来看看首次依赖收集的过程
前面提到过Vue2的兼容发生在applyOptions中,那么当data和setup中存在同名属性时,谁的优先级更高呢?
路径: packages\runtime-core\src\componentPublicInstance.ts
// 可以看到setup的优先级大于data
else if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
accessCache![key] = AccessTypes.SETUP
return setupState[key]
} else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
accessCache![key] = AccessTypes.DATA
return data[key]
}
可以看到setup优先级大于data,因此setup和data一起使用时,同名优先级setup大于data
ref在setup内要加上.value,而在模板中,只需要用ref就行了,这是在哪做了处理呢?
来自 handleSetupResult方法 路径: core\packages\runtime-core\src\component.ts
// From handleSetupResult:
// To proxyRefs:
// Return From proxyRefs: 如果是ref类型,返回一个新的ref代理对象,此代理对象上拥有get set方法,get方法返回ref.value,set方法赋值给ref.value,做一次代理,也就是模板内ref不用.value的原因
// 如果是对象,直接把值赋给instance.setupState,这就是用户的setup内部的状态
// 并对ref做一层代理
instance.setupState = proxyRefs(setupResult)
可以看到在handleSetupResult方法中,对setup调用的结果执行了proxyRefs
proxyRefs方法 路径: core\packages\reactivity\src\ref.ts
// From handleSetupResult:
// Return To handleSetupResult: 如果是ref类型,返回一个新的ref代理对象,此代理对象上拥有get set方法,get方法返回ref.value,set方法赋值给ref.value,做一次代理,也就是模板内ref不用.value的原因
export function proxyRefs<T extends object>(
objectWithRefs: T
): ShallowUnwrapRef<T> {
// From proxyRefs:
// To isReactive:
// Return From isReactive: 根据value上是否有ReactiveFlags.IS_REACTIVE属性判断是否是只读
return isReactive(objectWithRefs)
? // 如果是reactive对象,原样返回
objectWithRefs
: // From proxyRefs:
// To shallowUnwrapHandlers:
// Return From shallowUnwrapHandlers: 返回一个拥有get set方法的对象,get方法返回ref.value,set方法赋值给ref.value,做一次代理
new Proxy(objectWithRefs, shallowUnwrapHandlers)
}
// From shallowUnwrapHandlers:
// Return To shallowUnwrapHandlers: 脱ref,也就是如果是ref类型,就返回ref.value,等于做了一层代理,也就是模板中的ref可以不用.value的原因
export function unref<T>(ref: T | Ref<T>): T {
// 判断是否是ref类型 如果是的话返回ref.value 如果不是的话原样返回
return isRef(ref) ? (ref.value as any) : ref
}
// From proxyRefs:
// Return To proxyRefs:
const shallowUnwrapHandlers: ProxyHandler<any> = {
// From shallowUnwrapHandlers:
// To unref:
// Return From unref: 对ref做一层代理,也就是模板中的ref可以不用.value的原因
// get方法会返回ref.value unref对ref做了一层代理,也就是模板中的ref可以不用.value的原因
get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),
//
set: (target, key, value, receiver) => {
// 获取老值
const oldValue = target[key]
// 如果老值是ref类型 并且新值不是ref类型
if (isRef(oldValue) && !isRef(value)) {
// 将新值赋值给老值的value
oldValue.value = value
return true
} else {
// 返回Reflect.set的返回结果
return Reflect.set(target, key, value, receiver)
}
}
}
proxyRefs方法做了什么?
如果是ref类型的话,就返回一个拥有get和set方法的代理对象,get方法通过unref进行脱ref处理,也就是返回ref.value,set方法赋值给ref.value
也就是说在proxyRefs中,完成了对ref类型的代理,因此在模板中使用的是经过代理后的ref了,因此不需要使用.value
来看看目录结构,这些都不多已经逐行注释分析完了,除了deferredComputed,但这个不是我们需要分析的,尤大关于deferredComputed解释
注意:这不是 Vue API 的一部分,仅作为特定于 @vue/reactivity 的较低级别的 API
这一篇响应式原理文章,几乎是行行有注释,大家有不理解的地方可以参考我的github,也感谢大家支持!