您现在的位置是:网站首页> 编程资料编程资料
Vue3系列之effect和ReactiveEffect track trigger源码解析_vue.js_
2023-05-24
333人已围观
简介 Vue3系列之effect和ReactiveEffect track trigger源码解析_vue.js_
引言
介绍几个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() 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;onTrackonTrigger:这两个listener为调试用,分别在依赖收集和响应式更新时触发;- run:
effect最核心的方法。 stop:调用cleanupEffect让effect停止起作用,如果是stop当前活跃的effect,也就是自己停止自己,则会将deferStop调为true,从而延迟停止的时机;触发onStop;将active调为false。
export class ReactiveEffect{ active = true deps: Dep[] = [] parent: ReactiveEffect | undefined = undefined /** * Can be attached after creation * @internal */ computed?: ComputedRefImpl /** * @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 effect: ReactiveEffect } 2. 函数声明
effect函数有两个入参:
fn:是一个函数,经处理后用于创建ReactiveEffect实例_effect;options:可选,用于覆盖_effect上的属性。
export function effect( 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() 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的首字母,表示当前依赖是否是新收集的。 提示:
本文由神整理自网络,如有侵权请联系本站删除!
本站声明:
1、本站所有资源均来源于互联网,不保证100%完整、不提供任何技术支持;
2、本站所发布的文章以及附件仅限用于学习和研究目的;不得将用于商业或者非法用途;否则由此产生的法律后果,本站概不负责!
相关内容
- JavaScript前后端数据交互工具ajax使用教程_javascript技巧_
- 时间处理工具 dayjs使用示例详解_javascript技巧_
- vue3渲染函数(h函数)的变更剖析_vue.js_
- JavaScrip如何安全使用Payment Request API详解_javascript技巧_
- React使用useEffect解决setState副作用详解_React_
- react context优化四重奏教程示例_React_
- Vue $nextTick 为什么能获取到最新Dom源码解析_vue.js_
- JavaScript语法 JSON序列化之stringify实例详解_javascript技巧_
- JavaScript 转义字符JSON parse错误研究_javascript技巧_
- React Native可定制底板组件Magic Sheet使用示例_React_
