vue3源码(V3.0.0)解读---effect

116 阅读4分钟

版本V3.0.0 源码位置: packages -> reactivity -> src -> effect.ts

effect

const effectStack: ReactiveEffect[] = [] 
let activeEffect: ReactiveEffect | undefined

export function isEffect(fn: any): fn is ReactiveEffect { 
    return fn && fn._isEffect === true 
}

export function effect<T = any>( 
fn: () => T, 
options: ReactiveEffectOptions = EMPTY_OBJ 
): ReactiveEffect<T> { 
    if (isEffect(fn)) { 
        fn = fn.raw 
    } 
    const effect = createReactiveEffect(fn, options) 
    if (!options.lazy) { 
        effect() 
    } 
    return effect 
}

主要功能创建一个会产生副作用的函数。

  • fn: 传入一个函数
  • options
    首先判断传入的函数是不是副作用函数,如果是直接返回函数就可以

createReactiveEffect

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

export function enableTracking() { 
    trackStack.push(shouldTrack) 
    shouldTrack = true 
}

export interface ReactiveEffect<T = any> { 
    (): T 
    _isEffect: true 
    id: number 
    active: boolean 
    raw: () => T 
    deps: Array<Dep> 
    options: ReactiveEffectOptions 
}

function createReactiveEffect<T = any>( 
    fn: () => T, 
    options: ReactiveEffectOptions 
): ReactiveEffect<T> { 
    const effect = function reactiveEffect(): unknown { 
        if (!effect.active) { 
            return options.scheduler ? undefined : fn() 
        } 
        if (!effectStack.includes(effect)) { 
            cleanup(effect) 
            try { 
                enableTracking() 
                effectStack.push(effect)  
                activeEffect = effect 
                return fn() 
            } finally { 
                effectStack.pop() 
                resetTracking() 
                activeEffect = effectStack[effectStack.length - 1] 
            } 
        } 
    } as ReactiveEffect 
    
    effect.id = uid++ 
    effect._isEffect = true 
    effect.active = true 
    effect.raw = fn 
    effect.deps = [] 
    effect.options = options 
    return effect 
}

createReactiveEffect函数是响应式系统的核心之一,用于创建和管理响应式副作用

为这个函数添加属性,

  • id: 一个唯一标识符,用于区分不同的响应式副作用
  • _isEffect: 一个布尔值,标记这个对象是一个响应式副作用。
  • active: 一个布尔值,表示这个副作用当前是否处于活动状态。
  • raw: 存储原始的副作用函数。
  • deps: 存储这个副作用依赖的所有响应式依赖项。
  • options: 存储创建副作用时传入的配置选项。

函数内部 首先判断active是否激活,如果没有激活根据是否存在调度器(scheduler)确定返回的数据

接下来依赖收集和清理

  1. 如果当前的副作用依赖于effectStack中,则执行清理操作cleanup(effect),移除之前可能存在的依赖
  2. 通过enableTracking()开启依赖收集,将当前副作用压入依赖栈effectStack,并将activeEffect设置为当前副作用,然后执行副作用函数fn()
  3. 然后删除依赖栈中的当前副作用函数,并且停止依赖收集,设置当前activeEffect数据为依赖栈的最后一项。

track

依赖收集

const targetMap = new WeakMap<any, KeyToDepMap>()

export function track(target: object, type: TrackOpTypes, key: unknown) {

    if (!shouldTrack || activeEffect === undefined) { 
        return 
    } 
    let depsMap = targetMap.get(target) 
    if (!depsMap) { 
        targetMap.set(target, (depsMap = new Map())) 
    } 
    let dep = depsMap.get(key) 
    if (!dep) { 
        depsMap.set(key, (dep = new Set())) 
    }

    if (!dep.has(activeEffect)) { 
        dep.add(activeEffect) 
        activeEffect.deps.push(dep) 
        if (__DEV__ && activeEffect.options.onTrack) { 
            activeEffect.options.onTrack({ 
                effect: activeEffect, 
                target, 
                type, 
                key 
            }) 
        } 
    } 
}
  • shouldTrack 开始追踪依赖
  • targetMap 响应式依赖集合
  1. 如果暂停依赖追踪或者activeEffect没有值就return。停止后续操作
  2. 获取当前target的依赖集合,如果没有就创建一个集合并存targetMap中去
  3. 获取当前key的依赖追踪集合,如果没有就创建一个集合并存入depsMap中
  4. 判断当前的activeEffect是否收集过,如果没有收集过,就把当前的activeEffect存入到dep中
  5. 并在activeEffect中的deps中push进dep
  6. 如果activeEffect的.options.onTrack(在响应属性或引用作为依赖项被跟踪时被调用)存在,就触发onTrack有值并传入相应的数据

trigger

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 
    } 
    const effects = new Set<ReactiveEffect>() 
    const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => { 
        if (effectsToAdd) { 
            effectsToAdd.forEach(effect => { 
                if (effect !== activeEffect || effect.options.allowRecurse) { 
                    effects.add(effect) 
                }  
            }) 
        } 
    }
 
    if (type === TriggerOpTypes.CLEAR) { 
        // collection being cleared 
        // trigger all effects for target 
        depsMap.forEach(add) 
    } else if (key === 'length' && isArray(target)) { 
        depsMap.forEach((dep, key) => { 
            if (key === 'length' || key >= (newValue as number)) { 
                add(dep) 
            } 
        }) 
    } else { 
    // schedule runs for 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: 
            if (isMap(target)) { 
                add(depsMap.get(ITERATE_KEY)) 
            } 
            break 
        } 
    } 
    const run = (effect: ReactiveEffect) => { 
        if (__DEV__ && effect.options.onTrigger) { 
            effect.options.onTrigger({ 
                effect, 
                target, 
                key, 
                type, 
                newValue, 
                oldValue, 
                oldTarget 
            }) 
        } 
        if (effect.options.scheduler) { 
            effect.options.scheduler(effect) 
        } else {  
            effect() 
        } 
    }  
    effects.forEach(run) 
}
  • target // 触发变更的目标对象或集合
  • type // 触发类型(如添加、删除、设置值等)
  • key // 变更的键(对于对象或Map)或索引(对于数组)
  • newValue // 变更后的新值
  • oldValue // 变更前的旧值
  • oldTarget // 旧的目标集合,仅在某些特定操作中使用
  1. 从targetMap中取出target的依赖集合,如果没有就直接return
  2. 创建一个effects集合,
  3. 添加效果,定义一个add函数,用于将一组副作用添加到effects集合中。它检查每个副作用是否应该被触发
  4. 根据触发类型处理
  • CLEAR:如果触发类型是清除(例如,清空数组或集合),则触发目标对象的所有依赖效果。
  • LENGTH变更:如果目标是数组且变更的键是length,则触发与length相关的效果,以及所有索引大于或等于新length的效果。
  • SET | ADD | DELETE:对于设置、添加或删除操作,根据具体的键和类型,可能还需要触发迭代键的效果(例如,当数组添加新元素时,需要触发与数组长度相关的效果)。
  1. 定义一个run函数,用于执行每个效果。如果效果定义了onTrigger钩子(在侦听器回调被依赖项的变更触发时被调用),则先调用该钩子;然后,根据效果是否定义了调度器(scheduler),以同步或异步方式执行效果。