首页IT科技vue3.0 ui组件库(Vue3系列之effect和ReactiveEffect track trigger源码解析)

vue3.0 ui组件库(Vue3系列之effect和ReactiveEffect track trigger源码解析)

时间2025-06-18 02:02:45分类IT科技浏览4108
导读:引言 介绍几个API的时候,我们发现里面常出现effect、track和trigger,虽然简单说了下track用于依赖收集,trigger来触发更新。但是毕竟没看到具体实现,心里没底。如今便可以一探究竟。...

引言

介绍几个API的时候             ,我们发现里面常出现effect             、track和trigger                  ,虽然简单说了下track用于依赖收集      ,trigger来触发更新            。但是毕竟没看到具体实现             ,心里没底                   。如今便可以一探究竟      。

一                  、ReactiveEffect

1. 相关的全局变量

之前提到的effect                  ,便是ReactiveEffect的实例      。用到了一些重要的全局变量                   。

targetMap:弱映射      ,以目标对象target为key       ,其收集到的依赖集depsMap为值                  ,因此通过目标对象target可以获取到对应的所有依赖; activeEffect:当前活跃的effect            ,随后会被收集起来; shouldTrack:用作暂停和恢复依赖收集的标志; trackStack:历史shouldTrack的记录栈            。

targetMap对比reactive篇章中提到的proxyMap:

两者都是弱映射; 都以目标对象target为key; targetMap全局只有一个;而proxyMap有四种       ,分别对应reactive      、shallowReactive             、readonly                  、shallowReadonly; 一个target在一种proxyMap中最多只有一个对应的代理proxy                   ,因此proxyMap的值为单个的proxy对象; 一个target可以由很多的依赖dep            ,因此targetMap的值为数据集Map      。
const targetMap = new WeakMap<any, KeyToDepMap>() export let activeEffect: ReactiveEffect | undefined export let shouldTrack = true const trackStack: boolean[] = []

以及控制暂停      、恢复依赖收集的函数:

// 暂停收集 export function pauseTracking() { trackStack.push(shouldTrack) shouldTrack = false } // 恢复收集 export function enableTracking() { trackStack.push(shouldTrack) shouldTrack = true } // 重置为上一次的状态 export function resetTracking() { const last = trackStack.pop() shouldTrack = last === undefined ? true : last }

2. class 声明

在构造器中初始化fn ( 执行run()的过程中调用 )        、调度器scheduler,并通过recordEffectScope来记录实例的作用域;声明一些实例属性                   ,以及run                  、stop两个方法:

active:boolean类型                  ,表示当前的effect是否起作用; deps:当前effect的依赖; parent:指向上一个活跃的effect,形成链表; computed:可选             ,在computed函数得到的ComputedRefImpl里的effect具有这个属性; allowRecurse                  ,可选      ,表示是否允许自调用; deferStop:私有             ,可选                  ,表示stop()是否延迟执行; onStop:可选      ,函数       ,在执行stop()时会调用onStop; onTrack onTrigger:这两个listener为调试用                  ,分别在依赖收集和响应式更新时触发; run:effect最核心的方法                   。 stop:调用cleanupEffect让effect停止起作用            ,如果是stop当前活跃的effect       ,也就是自己停止自己                   ,则会将deferStop调为true            ,从而延迟停止的时机;触发onStop;将active调为false            。
export class ReactiveEffect<T = any> { active = true deps: Dep[] = [] parent: ReactiveEffect | undefined = undefined /** * Can be attached after creation * @internal */ computed?: ComputedRefImpl<T> /** * @internal */ allowRecurse?: boolean /** * @internal */ private deferStop?: boolean onStop?: () => void // dev only onTrack?: (event: DebuggerEvent) => void // dev only onTrigger?: (event: DebuggerEvent) => void constructor( public fn: () => T, public scheduler: EffectScheduler | null = null, scope?: EffectScope ) { recordEffectScope(this, scope) } run() { if (!this.active) { return this.fn() } // 当前活跃的effect let parent: ReactiveEffect | undefined = activeEffect let lastShouldTrack = shouldTrack // 如果当前活跃的effect就是这个effect本身,则直接返回 while (parent) { if (parent === this) { return } parent = parent.parent } // 依次活跃的effect形成链表                   ,由parent属性连接 try { this.parent = activeEffect activeEffect = this shouldTrack = true trackOpBit = 1 << ++effectTrackDepth if (effectTrackDepth <= maxMarkerBits) { // 遍历 this.deps 将其中的effect设置为已捕获 tracked initDepMarkers(this) } else { // 层级溢出则清除当前副作用 cleanupEffect(this) } // 尾调用传入的fn return this.fn() } finally { // 因为前面有return                  ,因此当 try 的代码块发生异常时执行 if (effectTrackDepth <= maxMarkerBits) { // 该方法遍历 this.deps,将其中过气的effect删除             ,未捕获的effect加入 // effect 就是其中的 dep finalizeDepMarkers(this) } trackOpBit = 1 << --effectTrackDepth // 复原一些状态 activeEffect = this.parent shouldTrack = lastShouldTrack this.parent = undefined // 若设置了延迟停止                  ,则执行stop      ,进行延迟清理 if (this.deferStop) { this.stop() } } } // 清除副作用 stop() { // stopped while running itself - defer the cleanup if (activeEffect === this) { this.deferStop = true } else if (this.active) { cleanupEffect(this) if (this.onStop) { this.onStop() } this.active = false } } }

3. cleanupEffect

cleanupEffect用于清除副作用。接收一个effect             ,遍历effect.deps                  ,并逐个删除副作用effect                   。随后清空effect.deps                   。

function cleanupEffect(effect: ReactiveEffect) { const { deps } = effect if (deps.length) { for (let i = 0; i < deps.length; i++) { deps[i].delete(effect) } deps.length = 0 } }

二            、effect 函数

1. 相关ts类型

effect函数有几个相关的类型:

ReactiveEffectOptions:effect函数的入参类型之一; ReactiveEffectRunner:是一个函数      ,且具有effect属性的类型;
export interface DebuggerOptions { onTrack?: (event: DebuggerEvent) => void onTrigger?: (event: DebuggerEvent) => void } export interface ReactiveEffectOptions extends DebuggerOptions { lazy?: boolean scheduler?: EffectScheduler scope?: EffectScope allowRecurse?: boolean onStop?: () => void } export interface ReactiveEffectRunner<T = any> { (): T effect: ReactiveEffect }

2. 函数声明

effect函数有两个入参:

fn:是一个函数       ,经处理后用于创建 ReactiveEffect实例_effect; options:可选                  ,用于覆盖_effect上的属性。
export function effect<T = any>( fn: () => T, options?: ReactiveEffectOptions ): ReactiveEffectRunner { // 处理fn if ((fn as ReactiveEffectRunner).effect) { fn = (fn as ReactiveEffectRunner).effect.fn } // 根据 fn 创建一个 _effect const _effect = new ReactiveEffect(fn) if (options) { // 用 options 覆盖 _effect 上的属性 extend(_effect, options) if (options.scope) recordEffectScope(_effect, options.scope) } // 没有 lazy , 则 _effect 立即执行一次 run() if (!options || !options.lazy) { _effect.run() } // runner:拿到 _effect.run 并挂上 effect 属性            ,包装成 ReactiveEffectRunner 类型 const runner = _effect.run.bind(_effect) as ReactiveEffectRunner // effect属性指回 _effect 自身       ,方便使用 runner 调用 run 和 stop runner.effect = _effect // 返回 runner return runner }

3. stop函数

stop用于清除effect            。入参为ReactiveEffectRunner;

export function stop(runner: ReactiveEffectRunner) { runner.effect.stop() }

三       、track 依赖收集

1. track

一直在说track进行依赖收集                   ,这里看下它到底怎么做的                   。

以目标对象target为key            ,depsMap为targetMap的值;以target的key为key,使用createDep()创建依赖dep为值                   ,存放在target对应的depsMap中      。 通过trackEffects(dep, eventInfo)来收集副作用            。
// 全局变量 targetMap const targetMap = new WeakMap<any, KeyToDepMap>() export function track(target: object, type: TrackOpTypes, key: unknown) { if (shouldTrack && activeEffect) { let depsMap = targetMap.get(target) if (!depsMap) { targetMap.set(target, (depsMap = new Map())) } let dep = depsMap.get(key) if (!dep) { depsMap.set(key, (dep = createDep())) } const eventInfo = __DEV__ ? { effect: activeEffect, target, type, key } : undefined trackEffects(dep, eventInfo) } }

2. createDep

使用createDep创建一个新的dep                   。可以看到                  ,dep是个Set实例,且添加了两个属性:

w:wasTracked的首字母             ,表示当前依赖是否被收集; n:newlyTracked的首字母                  ,表示当前依赖是否是新收集的      。
export const createDep = (effects?: ReactiveEffect[]): Dep => { const dep = new Set<ReactiveEffect>(effects) as Dep dep.w = 0 dep.n = 0 return dep }

3. trackEffects

trackEffects用于收集副作用      。主要把当前活跃的activeEffect加入dep      ,以及在activeEffect.deps中加入该副作用影响到的所有依赖                   。

export function trackEffects( dep: Dep, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { let shouldTrack = false if (effectTrackDepth <= maxMarkerBits) { if (!newTracked(dep)) { dep.n |= trackOpBit // set newly tracked shouldTrack = !wasTracked(dep) } } else { // Full cleanup mode. shouldTrack = !dep.has(activeEffect!) } // 当前依赖 dep 还未被捕获 / 当前依赖 dep 中             ,还没有当前活跃的副作用时                  , // 将当前活跃的副作用 effect 添加进 dep 里      ,同时在把 dep 加入受副作用影响的依赖集合 activeEffect.deps 中 if (shouldTrack) { dep.add(activeEffect!) activeEffect!.deps.push(dep) if (__DEV__ && activeEffect!.onTrack) { activeEffect!.onTrack({ effect: activeEffect!, ...debuggerEventExtraInfo! }) } } }

4. 小结

用一句比较拗口的话来说       ,依赖收集就是把当前活跃的副作用activeEffect存入全局变量targetMap中的 ( target 对应的 depsMap) 中 (target的key)对应的 dep ( 类型为Set) 中                  ,并把这个dep加入到受activeEffect副作用影响的所有依赖activeEffect.deps列表中            。

四                   、trigger

触发更新实际上就是触发副作用            ,因此这一小节决定以与track相反的顺序来介绍      。

1. triggerEffect

triggerEffect触发副作用从而更新                   。当触发更新的副作用effect允许自调用       ,且不是当前活跃的副作用时                   ,通过调度器scheduler执行副作用或者直接执行run            ,是实际上触发更新的地方            。

function triggerEffect( effect: ReactiveEffect, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { if (effect !== activeEffect || effect.allowRecurse) { if (__DEV__ && effect.onTrigger) { effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) } // 实际触发更新的地方 if (effect.scheduler) { effect.scheduler() } else { effect.run() } } }

2. triggerEffects

接收一个dep和用于调试的额外信息。遍历dep中的effect,逐一使用triggerEffect来执行副作用                   。源码在这里有点蜜汁操作                   。

export function triggerEffects( dep: Dep | ReactiveEffect[], debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { // spread into array for stabilization const effects = isArray(dep) ? dep : [...dep] // 两者互斥                   ,但是执行的操作相同?而且为什么不写在一个 for...of... 里 ? for (const effect of effects) { if (effect.computed) { triggerEffect(effect, debuggerEventExtraInfo) } } for (const effect of effects) { if (!effect.computed) { triggerEffect(effect, debuggerEventExtraInfo) } } }

3. trigger

之前一直说trigger触发更新                  ,其实是现在已经知道了,实际是triggerEffect来执行副作用从而实现更新。

这里是创建一个deps数组             ,根据target            、key和触发更新的操作类型type等参数                  ,来获取所有的相关dep      ,放入deps            。再取出deps中所有的dep里的所有effect             ,放入effects列表中                  ,通过triggerEffects(effects)来触发所有的相关副作用      ,最终实现更新                   。

需要注意的是对于数组:

修改length属性会导致该数组所有依赖的更新; 修数组新增成员会引起length属性相关的依赖的更新       ,因为length的值发生了变化      。
export function trigger( target: object, type: TriggerOpTypes, key?: unknown, newValue?: unknown, oldValue?: unknown, oldTarget?: Map<unknown, unknown> | Set<unknown> ) { const depsMap = targetMap.get(target) if (!depsMap) { // never been tracked return } // 用于聚集所有相关依赖 let deps: (Dep | undefined)[] = [] if (type === TriggerOpTypes.CLEAR) { // 调用了Set、Map实例的clear方法                  ,将触发全部相关的副作用 // collection being cleared // trigger all effects for target deps = [...depsMap.values()] } else if (key === length && isArray(target)) { // 目标对象是数组            ,且修改了length属性时       ,会触发全部相关的副作用 depsMap.forEach((dep, key) => { 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) { case TriggerOpTypes.ADD: if (!isArray(target)) { deps.push(depsMap.get(ITERATE_KEY)) if (isMap(target)) { deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)) } } else if (isIntegerKey(key)) { // 数组下标成员的更改 会引起 length 属性相关的更新 // new index added to array -> length changes deps.push(depsMap.get(length)) } break case TriggerOpTypes.DELETE: if (!isArray(target)) { deps.push(depsMap.get(ITERATE_KEY)) if (isMap(target)) { deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)) } } break case TriggerOpTypes.SET: if (isMap(target)) { deps.push(depsMap.get(ITERATE_KEY)) } break } } const eventInfo = __DEV__ ? { target, type, key, newValue, oldValue, oldTarget } : undefined if (deps.length === 1) { if (deps[0]) { if (__DEV__) { triggerEffects(deps[0], eventInfo) } else { triggerEffects(deps[0]) } } } else { const effects: ReactiveEffect[] = [] for (const dep of deps) { if (dep) { effects.push(...dep) } } // 这里triggerEffects接受的参数类型为Set                   ,之前的是数组 if (__DEV__) { triggerEffects(createDep(effects), eventInfo) } else { triggerEffects(createDep(effects)) } } }

五                   、小结

1. 依赖收集

targetMap中有depsMap(以target为key);depsMap中有许多dep(以targetMap的key为key);简单理解为:在编译时根据target和key            ,创建副作用,将activeEffect指向新建的副作用                   ,并存放到相关的依赖dep里的过程就是依赖收集            。

2. 触发更新

反过来                  ,触发target                  、key相关的dep中所有相关的副作用,通过各个副作用上的effect.scheduler()或者effect.run()来实现更新                   。

以上就是Vue3系列之effect和ReactiveEffect track trigger源码解析的详细内容             ,更多关于Vue3 effect和ReactiveEffect track trigger的资料请关注本站其它相关文章!

声明:本站所有文章                  ,如无特殊说明或标注      ,均为本站原创发布      。任何个人或组织             ,在未征得本站同意时                  ,禁止复制、盗用             、采集                  、发布本站内容到任何网站      、书籍等各类媒体平台      。如若本站内容侵犯了原著者的合法权益      ,可联系我们进行处理                   。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
win7鼠标右键不灵敏怎么办恢复(在win7系统中为什么鼠标右击桌面没反应?) mysql服务器优化的几种方法(MySQL 服务器调优)