Vue3源码系列——依赖收集和触发

589 阅读3分钟

依赖收集和触发ref和reactive分开讨论

公共部分

从patch开始

  1. patch触发了processComponent
  2. processComponent触发mountComponent
  3. mountComponent触发setupComponent、setupRenderEffect
  4. setupComponent中会执行setup函数,里面会有ref、reactive,并返回一个用来执行h的函数,赋值给instance.render(执行setup的时候就会把数据变成响应式的,但还没有收集
setup() {
  const refMsg = ref('1111')
  const refMsg2 = ref('3333')
  return () => {
    return h("div", {}, `111${refMsg.value}`);
  };
},
const setupResult = setup && setup(shallowReadonly(instance.props), setupContext);
instance.render = setupResult;

setupRenderEffect函数中回执行

12行执行run,我们要拿出来重点分析

function componentUpdateFn() {
  const subTree = (instance.subTree = instance.render.call(proxyToUse, proxyToUse));
}
instance.update = effect(componentUpdateFn, {
    scheduler: () => {
        queueJob(instance.update);
    },
});
// effect函数
function effect(fn, options = {}) {
    const _effect = new ReactiveEffect(fn);
    _effect.run();
    const runner = _effect.run.bind(_effect);
}

收集依赖的类ReactiveEffect

每次setup都会new ReactiveEffect(); 在ReactiveEffect中有deps收集依赖,也就把diff控制在了组件级。

class ReactiveEffect {
    constructor(fn, scheduler) {
        this.fn = fn;
        this.scheduler = scheduler;
        this.active = true;
        this.deps = [];
    }
    run() {
        if (!this.active) {
            return this.fn();
        }
        shouldTrack = true;
        activeEffect = this;
        const result = this.fn();
        shouldTrack = false;
        activeEffect = undefined;
        return result;
    }
    stop() {
        if (this.active) {
            cleanupEffect(this);
            if (this.onStop) {
                this.onStop();
            }
            this.active = false;
        }
    }
}

ReactiveEffect中run

可以看到const result = this.fn();前后的操作,我们可以理解为:收集依赖的开关,this.fn()之前开启,之后关闭。那么this.fn()做了什么(fn就是上面的componentUpdateFn)

run() {
    if (!this.active) {
        return this.fn();
    }
    shouldTrack = true;
    activeEffect = this;
    const result = this.fn();
    shouldTrack = false;
    activeEffect = undefined;
    return result;
}

执行h函数触发get,componentUpdateFn

通过isMounted做了区分:挂载过、未挂载过。但是两种都会执行instance.render,也就回执行setup函数返回的h函数。

h函数中如果用到了响应式的数据,则回触发该数据的get,下面我们分析get

function componentUpdateFn() {
    if (!instance.isMounted) {
        const proxyToUse = instance.proxy;
        const subTree = (instance.subTree = instance.render.call(proxyToUse, proxyToUse));
        patch(null, subTree, container, instance);
        initialVNode.el = subTree.el;
        instance.isMounted = true;
    }
    else {
        const { next, vnode } = instance;
        if (next) {
            next.el = vnode.el;
            updateComponentPreRender(instance, next);
        }
        const proxyToUse = instance.proxy;
        const nextTree = instance.render.call(proxyToUse, proxyToUse);
        const prevTree = instance.subTree;
        instance.subTree = nextTree;
        patch(prevTree, nextTree, prevTree.el, instance);
    }
}
setup() {
  const refMsg = ref('1111')
  const refMsg2 = ref('3333')
  return () => {
    return h("div", {}, `111${refMsg.value}`);
  };
},

ref

get收集依赖

class RefImpl {
    constructor(value) {
        this.__v_isRef = true;
        this._rawValue = value;
        this._value = convert(value);
        this.dep = createDep();
    }
    get value() {
        // console.log('触发get,收集')
        trackRefValue(this);
        return this._value;
    }
    set value(newValue) {
        if (hasChanged(newValue, this._rawValue)) {
            this._value = convert(newValue);
            this._rawValue = newValue;
            triggerRefValue(this);
        }
    }
}

trackRefValue

  1. trackRefValue参数是RefImpl的实例,isTracking是个开关,shouldTrack=true,activeEffect是ReactiveEffect的实例。
  2. trackEffects把activeEffect添加进RefImpl的dep中。每个RefImpl都保存了当前组件所有的依赖。
  3. trackEffects把RefImpl的dep放进了activeEffect的deps中,每个ref都会在activeEffect.deps中放进一个依赖。
function trackRefValue(ref) {
    if (isTracking()) {
        trackEffects(ref.dep);
    }
}

function isTracking() {
    return shouldTrack && activeEffect !== undefined;
}

function trackEffects(dep) {
    if (!dep.has(activeEffect)) {
        dep.add(activeEffect);
        activeEffect.deps.push(dep);
    }
}

set触发依赖

class RefImpl {
    constructor(value) {
        this.__v_isRef = true;
        this._rawValue = value;
        this._value = convert(value);
        this.dep = createDep();
    }
    get value() {
        trackRefValue(this);
        return this._value;
    }
    set value(newValue) {
        if (hasChanged(newValue, this._rawValue)) {
            this._value = convert(newValue);
            this._rawValue = newValue;
            // console.log('触发依赖')
            triggerRefValue(this);
        }
    }
}

triggerRefValue

function triggerRefValue(ref) {
    triggerEffects(ref.dep);
}

function triggerEffects(dep) {
    for (const effect of dep) {
        if (effect.scheduler) {
            effect.scheduler();
        }
        else {
            effect.run();
        }
    }
}

scheduler

当!queue.includes(job),才会执行queueFlush,也就意味着组件触发多个依赖时,只会触发一次更新

instance.update = effect(componentUpdateFn, {
    scheduler: () => {
        queueJob(instance.update);
    },
});

function queueJob(job) {
    if (!queue.includes(job)) {
        queue.push(job);
        queueFlush();
    }
}

function flushJobs() {
    isFlushPending = false;
    let job;
    while ((job = queue.shift())) {
        if (job) {
            job();
        }
    }
}

function queueFlush() {
    if (isFlushPending)
        return;
    isFlushPending = true;
    nextTick(flushJobs);
}

function nextTick(fn) {
    return fn ? p.then(fn) : p;
}

还记得第19行job是什么吗?

job就是effect执行时返回的runner,用来执行run

22行的fn正是componentUpdateFn,会执行patch

instance.update = effect(componentUpdateFn, {
    scheduler: () => {
        queueJob(instance.update);
    },
});
function effect(fn, options = {}) {
    const _effect = new ReactiveEffect(fn);
    extend(_effect, options);
    _effect.run();
    const runner = _effect.run.bind(_effect);
    runner.effect = _effect;
    return runner;
}
//简化过
class ReactiveEffect {
    constructor(fn, scheduler) {
        this.fn = fn;
    }
    run() {
        shouldTrack = true;
        activeEffect = this;
        const result = this.fn();
        shouldTrack = false;
        activeEffect = undefined;
        return result;
    }
 }

reactive

get收集依赖

const proxy = new Proxy(target, baseHandlers);
baseHandlers = {
  get: 
}
function createGetter(isReadonly = false, shallow = false) {
    return function get(target, key, receiver) {
        ...
        if (!isReadonly) {
            track(target, "get", key);
        }
        ...
    };
}

track函数

  1. targetMap存储依赖
  2. isTracking作用和上面在ref中一样
  3. targetMap中存一个数据:key是target,value是一个depsMap(类型Map)
  4. 给上面的Map存入数据:key是参数key,value是dep(类型Set)
const targetMap = new WeakMap();
function track(target, type, key) {
    if (!isTracking()) {
        return;
    }
    // console.log(`触发 track -> target: ${target} type:${type} key:${key}`);
    let depsMap = targetMap.get(target);
    if (!depsMap) {
        depsMap = new Map();
        targetMap.set(target, depsMap);
    }
    let dep = depsMap.get(key);
    if (!dep) {
        dep = createDep();
        depsMap.set(key, dep);
    }
    trackEffects(dep);
}

trackEffects函数

activeEffect就是ReactiveEffect的实例,把activeEffect存在targetMap中通过target、key获取

function trackEffects(dep) {
    if (!dep.has(activeEffect)) {
        dep.add(activeEffect);
        activeEffect.deps.push(dep);
    }
}

set触发依赖

function createSetter() {
    return function set(target, key, value, receiver) {
        const result = Reflect.set(target, key, value, receiver);
        trigger(target, "get", key);
        return result;
    };
}

trigger函数

  1. 通过target、key从targetMap中获取dep
  2. 把dep放进effects中,用createDep去重一下
function trigger(target, type, key) {
    let deps = [];
    const depsMap = targetMap.get(target);
    const dep = depsMap.get(key);
    deps.push(dep);
    const effects = [];
    deps.forEach((dep) => {
        effects.push(...dep);
    });
    triggerEffects(createDep(effects));
}

function createDep(effects) {
    const dep = new Set(effects);
    return dep;
}

triggerEffects函数

变量执行effect.scheduler();

function triggerEffects(dep) {
    for (const effect of dep) {
        if (effect.scheduler) {
            effect.scheduler();
        }
        else {
            effect.run();
        }
    }
}

scheduler同上面的ref中的scheduler

总结

ref和reactive的区别在于依赖存储位置不同,触发依赖时拿取依赖的位置也就不同

  • ref是存在RefImpl实例中
  • reactive是存在公共targetMap中