vue3.x reactive、effect、computed、watch依赖关系及实现原理

4,301 阅读4分钟

首先来了解2个全局常量和1个变量。

  • targetMap [new WeakMap] : 在track()、trigger()的时候,冲当中间关联容器
  • effectStack [Array] : 顾名思义就是存放effect的容器
  • activeEffect : 在track()的时候作为Target的收集对象,可以当作一个临时作用的变量

1、reactive(target)

reactive实现原理,不废话(bb)直接上图 reactive实现原理

  • 原理大致就是通过 new Proxy() 重写getter、setter方法来实现自己需要的逻辑
  • 重写getter实现track收集effect
  • 重写setter现实trigger触发effect

2、effect(fn,options)

effect实现原理

effect是作为track()过程中最重要的环节,是唯一提供activeEffect的地方

3、computed(getterOrOptions)

computed实现原理

通过上面流程图不难看出computed的实现本质是effect.

  • 1.通过处理getterOrOptions参数得到一个匿名函数 getter
  • 2.把getter作为 effect() 参数得到一个延迟执行的runner函数
  • 3.把runner封装成一个 ref 对象并且返回。

4、watch(source,cb,options)

watch实现原理

相比 computed ,watch的实现逻辑相对复杂一些。这里只讲解大致实现原理。通过分析源码发现,watch的大致原理也是建立在 effect 的实现上。

  • 1.首先一些列处理把source 封装成为 effect的fn参数,例如:const getter = ()=> source;
  • 2.通过effect得到一个延迟的runner函数
  • 3.首次执行runner完成track()
  • 4.返回一个停止监听的匿名函数

5、简单实现

重点说明: 以下代码( 省略了很多逻辑 )的实现仅供原理理解,具体实现请移步vue-next源码。

5.1、reactive

通过1、reactive(target)的分析和理解,reactive大致实现就是这样:

function reactive(obj) {
    return new Proxy(obj, {
        get: function(target, key, receiver) {
        	// getter
            track(target, key); // 收集effect函数
            return Reflect.get(target, key, receiver);
        },
        set: function(target, key, value, receiver) {
        	// setter
            let oldValue = target[key]; // 原始值
            if (oldValue === value) {
                return;
            }
            let res = Reflect.set(target, key, value, receiver);
            trigger(target, key); // 触发effect函数
            return res;
        },
    });
}

不了解Reflect的同学自行百度,这里不讲述。接下来实现track、trigger函数

//...
const targetMap = new WeakMap();
const effectStack = [];
let activeEffect = null;

function track(target, key) {
    if (!activeEffect) {
        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);
    }
}

function trigger(target, key) {
    // 触发
    const depsMap = targetMap.get(target);
    if (!depsMap) {
        return;
    }

    let dep = depsMap.get(key);

    let effects = new Set();
    dep && dep.forEach((effect) => effects.add(effect));

    // 执行
    effects.forEach((effect) => {
        if (effect.options.scheduler) {
            effect.options.scheduler(effect);
        } else {
            effect();
        }
    });
}

上面就是整个reactive实现的大致代码。虽然不够完整,但是够理解了。想要深入了解的同学可以直接移步vue-next源码。

5.2、effect

接下来就是 effect(fn,options) 的实现。代码如下:

const effectStack = [];
let activeEffect = null;
//...
function effect(fn, options = {}) {
    let effect = createEffect(fn, options);
    if (!options.lazy) {
        effect();
    }
    return effect;
}

let uid = 0;
function createEffect(fn, options) {
    const effect = () => {
        if (!effectStack.includes(effect)) {
            try {
                effectStack.push(effect);
                activeEffect = effect;
                // 默认执行收集
                return fn();
            } finally {
                // 处理完成
                effectStack.pop();
                activeEffect = effectStack[effectStack.length - 1];
            }
        }
    };
    effect.raw = fn;
    effect.id = uid++;
    effect.options = options;
    //...
    return effect;
}

通过 effect(fn,options) 可以分析出effect的执行返回的是fn()的结果。如果吧reactive、effect放在一起你会发现他们的依赖关系。Proxy 通过getter收集activeEffect、setter触发activeEffect。看似两个完成独立的方法,中间通过全局变量activeEffect联系在一起。

5.3、computed

reactive和effect的实现之后computed就相对简单多了。computed的实现代码:

//...
function computed(fn) {
    let runner = effect(fn, {
        lazy: true,
    });

    return {
        get value() {
            // 执行收集
            return runner();
        },
        set value(value) {},
    };
}

// 使用
let data = reactive({ count : 0 })

let text = computed(() => {
    return `count:${data.count}`;
});

console.log(text.value) // count:0

对,没错。computed的实现就是这么简单。相比computed、watch的实现就复杂多了。

5.3、watch

// 这里只是简单的实现watch以供理解

function watch(source, cb) {
    let getter = () => {};
    if (isFunction(source)) {
        getter = source;
    }
    // 收集信息
    let runner = effect(getter, {
        lazy: true,
        scheduler: () => {
            // 执行回调
            let value = runner();
            if (value !== oldValue) {
                cb(oldValue, value);
            }
            oldValue = value;
        },
    });
    // 第一次执行收集
    let oldValue = runner();
    //停止监听
    return ()=>{
    	stop(runner)
        //...
    }
}

// 使用
let stopWatcher = watch(
    () => data.count,
    (oldValue, value) => {
        // 执行
        console.log("========watch========", oldValue, value);
    }
);

// 可以停止watch
// stopWatcher()

data.count = 100;
// ========watch======== 0 100

5.4、完整的代码

// reactive
function reactive(obj) {
    return new Proxy(obj, {
        get: function(target, key, receiver) {
            track(target, key);
            // getter
            return Reflect.get(target, key, receiver);
        },
        set: function(target, key, value, receiver) {
            let oldValue = target[key]; // 原始值
            if (oldValue === value) {
                return;
            }

            // setter
            let res = Reflect.set(target, key, value, receiver);
            trigger(target, key);
            return res;
        },
    });
}

const targetMap = new WeakMap();
const effectStack = [];
let activeEffect = null;
// track
function track(target, key) {
    if (!activeEffect) {
        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);
    }
}
// trigger
function trigger(target, key) {
    // 触发
    const depsMap = targetMap.get(target);
    if (!depsMap) {
        return;
    }

    let dep = depsMap.get(key);

    let effects = new Set();
    dep && dep.forEach((effect) => effects.add(effect));

    // 执行
    effects.forEach((effect) => {
        if (effect.options.scheduler) {
            effect.options.scheduler(effect);
        } else {
            effect();
        }
    });
}
// effect
function effect(fn, options = {}) {
    let effect = createEffect(fn, options);
    if (!options.lazy) {
        // 执行手机
        effect();
    }

    return effect;
}

let uid = 0;
function createEffect(fn, options) {
    const effect = () => {
        if (!effectStack.includes(effect)) {
            try {
                effectStack.push(effect);
                activeEffect = effect;
                // 首次执行收集
                return fn();
            } finally {
                // 处理完成
                effectStack.pop();
                activeEffect = effectStack[effectStack.length - 1];
            }
        }
    };
    effect.raw = fn;
    effect.id = uid++;
    effect.options = options;
    return effect;
}
// computed
function computed(fn) {
    let runner = effect(fn, {
        lazy: true,
    });

    return {
        get value() {
            // 执行收集
            return runner();
        },
        set value(value) {},
    };
}
// watch
function watch(source, cb) {
    let getter = () => {};
    if (typeof source === "function") {
        getter = source;
    }
    // 收集信息
    let runner = effect(getter, {
        lazy: true,
        scheduler: () => {
            // 执行回调
            let value = runner();
            if (value !== oldValue) {
                cb(oldValue, value);
            }
            oldValue = value
        },
    });
    // 第一次执行收集
    let oldValue = runner();
}

6、总结

通过简单的实现可以总结出一下几点:

  • vue核心是重写new Proxy()的getter、setter来实现数据驱动的核心
  • computed和watch的实现可以看出实际原理都依赖effect()工厂
  • activeEffect可以看作是new Proxy()和effec()的交通枢纽

再次声明:上述实现代码仅供理解实现原理,具体详细实现请移步vue-next源码。

萌新,如有错误欢迎指正 (ง •_•)ง