前端分享--vue3.x源码系列【干货】

137 阅读1分钟

响应式系统的核心就是一个 WeakMap --- Map --- Set 的数据结构。

track 收集依赖(涉及到了es6的三种数据结构即WeakMap,Map,Set) trigger 触发依赖(拿出所有依赖,每一个依赖就是一个副作用函数,所以直接调用即可) effect副作用函数(执行每一个回调函数)

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>mini vue3.X</title>
</head>

<body>
    <div id="app"></div>
</body>

</html>

<script>


    function isObject(data) {
        return data && typeof data === 'object';
    }

    //全局变量表示依赖
    let activeEffect;
    //存储依赖的数据结构
    let targetMap = new WeakMap();
    /**
     *  
        track方法就是用来收集依赖的。我们用es6的weakMap数据结构来存储依赖,
        然后为了简便化用一个全局变量来表示依赖 
        每一个依赖又是一个map结构,每一个map存储一个副作用函数即effect函数
        收集依赖就这么简单,需要注意的是,这里涉及到了es6的三种数据结构即WeakMap,Map,Set
    */
    function track(target, key) {
        //拿到依赖
        let depsMap = targetMap.get(target);
        // 如果依赖不存在则初始化
        if (!depsMap) targetMap.set(target, (depsMap = new Map()));
        //拿到具体的依赖,是一个set结构
        let dep = depsMap.get(key);
        if (!dep) depsMap.set(key, (dep = new Set()));
        //如果没有依赖,则存储再set数据结构中
        if (!dep.has(activeEffect)) dep.add(activeEffect)
    }

    //trigger方法很明显就是拿出所有依赖,每一个依赖就是一个副作用函数,所以直接调用即可
    function trigger(target, key) {
        let depsMap = targetMap.get(target);
        //存储依赖的数据结构都拿不到,则代表没有依赖,直接返回
        if (!depsMap) return;
        depsMap.get(key).forEach(e => e && e());
    }

    //副作用函数的作用也很简单,就是执行每一个回调函数。所以该方法有2个参数,
    //第一个是回调函数,第二个则是一个配置对象
    function effect(fn, options = {}) {
        const __effect = function (...args) {
            activeEffect = __effect;
            return fn(...args);
        }
        //配置对象有一个lazy属性,用于computed计算属性的实现,因为计算属性是懒加载的,也就是延迟执行
        //也就是说如果不是一个计算属性的回调函数,则立即执行副作用函数
        if (!options.lazy) {
            __effect();
        }
        return __effect;
    }

    function computed(fn) {
        // 只考虑函数的情况
        // 延迟计算 const c = computed(() => `${ count.value}!`)
        let __computed;
        //可以看到computed就是一个添加了lazy为true的配置对象的副作用函数
        const run = effect(fn, { lazy: true });
        __computed = {
            //get 访问器
            get value() {
                return run();
            }
        }
        return __computed;
    }
    /**
     * 对象 { count:0 }
     * @param {*} data 
     * @returns 
     */
    function reactive(data) {
        if (!isObject(data)) return;
        return new Proxy(data, {
            get(target, key, receiver) {
                //反射api
                const ret = Reflect.get(target, key, receiver);
                //收集依赖
                track(target, key);
                return isObject(ret) ? reactive(ret) : ret;
            },
            set(target, key, val, receiver) {
                Reflect.set(target, key, val, receiver);
                //触发依赖
                trigger(target, key);
                return true;
            },
            deleteProperty() {
                const ret = Reflect.deleteProperty(target, key, receiver);
                trigger(target, key);
                return ret;
            }
        })
    }

    /**
     * 基本类型
     * const count = ref(0);
     * count.value++
     */
    function ref(target) {
        let value = target;
        const obj = {
            get value() {
                //收集依赖
                track(obj, 'value');
                return value;
            },
            set value(newValue) {
                if (value === newValue) return;
                value = newValue;
                //触发依赖
                trigger(obj, 'value');
            }
        }
        return obj;
    }


    function mount(instance, el) {
        effect(function () {
            instance.$data && update(instance, el);
        });
        //setup返回的数据就是实例上的数据
        instance.$data = instance.setup();
        //这里的update实际上就是编译函数
        update(instance, el);
    }



    // compile编译的实现很水
    function update(instance, el) {
        el.innerHTML = instance.render();
    }




</script>

<script>
    const App = {
        $data: null,
        setup() {
            let count = ref(0);
            let time = reactive({ second: 0 });
            let com = computed(() => `${count.value + time.second}`);
            setInterval(() => {
                time.second++;
            }, 1000);
            setInterval(() => {
                count.value++;
            }, 2000);
            return {
                count,
                time,
                com
            }
        },
        render() {
            return `
                <h1>How reactive?</h1>
                <p>this is reactive work:${this.$data.time.second}</p>
                <p>this is ref work:${this.$data.count.value}</p>
                <p>this is computed work:${this.$data.com.value}</p>
            `
        }
    }
    mount(App, document.querySelector("#app"));
</script>