Vue响应式

78 阅读1分钟

试着写一个简单的

    export function Vue(options = {}){
        this.__init(options);
    }
    
    //initMixin
    Vue.prototype.__init function(options){
        this.$options = options;
        //假设已经是一个el ,已经querySelector的
        this.$el = options.el;
        this.$data = options.data;
        this.$methods = options.methods;
        
        //beforeCreate -- initState  -- initData;
        proxy(this, this.$data);
        
        //observer()
        observer(this.$data);
        new Compiler(this);
    }
    //this.$data.message  -> this.message
    
    function proxy(target, data){
        Object.keys(data).forEach(key =>{
            Object.defineProperty(target, key, {
                enumerabke: true,
                configurable: true,
                get(){
                    return data[key],
                },
                set(newVal){
                    //考虑NaN的情况
                    if(!isSameVal(data[key], newVal)){
                        data[key] = newVal
                    }
                }
            })
        })
    }
    
    function observer(target, data){
        new Observer(data);
    }
    
    class Observer{
        constructor(data){
            this.walk(data);
        }
        
        walk(data){
            if(data && typeof data === 'object'){
                Object.keys(data).forEach(key=> this.defineReactive(data, key,data[key]));
            }
        }
        
        //收集data里的数据
        defineReactive(obj, key, value){
            let that = this
            this.walk(value);
            let dep = new Dep();
            Object.defineProperty(obj, key, {
                configurable: true,
                enumerable: true,
                get(){
                    Dep.target && dep.add(Dep.target)
                    return value
                },
                set(newVal){
                    if(!isSameVal(value, newVal)){
                        //新赋值的没有响应式所以要walk一次给到响应式
                        value = newVal;
                        that.walk(newVal);
                        dep.notify();
                    }
                }
            })
        }
    }
    
    //视图怎么更新
    //数据改变,视图更新,需要观察
    class Watcher{
        constructor(vm, key, cb){
            this.vm = vm;
            this.key = key;
            this.cb = cb;
            
            Dep.target = this;
            this._old = vm[key];
            Dep.target = null;
        }
        //执行所有的cb
        update(){
            let newVal = this.vm[this.key];
            if(!isSameVal(newVal, this._old)) this.cb(newVal);
        }
    }
    
    
    //每一个数据都需要有一个Dep
    class Dep{
        constructor(){
            this.deps = new Set();
        }
        
        add(watcher){
            if(watcher && watcher.update) this.watchers.add(watcher);
        }
        
        //让所有的watcher 执行 update方法
        notify(){
            this.watchers.forEach(watc => watc.update())
        }
    }
    
    class Compiler{
        constructor(vm){
            this.el = vm.$el;
            this.vm = vm;
            this.methdos = vm.$methods;
            
            this.compile(vm.$el);
        }
        
        //递归编译#app下面的所有节点内容
        compile(el){
            let childNodes = el.childNodes;
            //类数组
            Array.from(childNodes).forEach(node =>{
                if(node.nodeType === 3){
                    this.compileText(node);
                }esle if(node.nodeType === 1){
                    this.compileElement(node);
                }
                if(node.childNode && node.childNodes.length) this.compile(node);
                //
            })
        }
        
        compileText(node){
            //匹配{{ msg }}
            let reg = /\{\{(.+?)\}\}/;
            let value = node.textContent;
            if(reg.text(value)){
                let key = RegExp.$1.trim()
                node.textContent = value.replace(reg, this.vm[key]);
                new Watcher(this.vm, key, val=>{
                    node.textContent = val
                })
            }
        }
        
        compileElement(node){
            //处理指令,这里只匹配 v-on 和 v-model 
            if(node.attributes.length){
                Array.from(node.attributes).forEach(att =>{
                    let attrName = attr.name;
                    if(attrName.startsWith('v-')){
                        attrName = attrName.indexOf(':') > -1 ? attrName.substr(5) : attrName.substr(2)
                        let key attr.value;
                        this.update(node, key, attrName, this.vm[key]);
                    }
                })
            }
        }
        update(node, key, attrName, value){
            if(attrName === 'model'){
                node.value = value;
                new Watcher(this.vm, key, val => node.value = val);
                node.addEventListener('input', ()=>{
                    this.vm[key] = node.value;
                })
            }else if(attrName === 'click'){
                node.addEventListener(attrName, this.methods[key].bind(this.vm))
            }
        }
    }
    
    function isSameVal(a, b){
        return a === b || (Number.isNaN(a)&& Number.isNaN(b))
    }
    

截屏2023-05-18 10.13.19.png

Vue2.x和Vue3.x对比

  • Vue 3.x 使用了proxy作为响应式,天生的代理,不用考虑属性重写,数组这些2.x中hack的情况;

  • diff,增加了最大递增子序列的算法,让我移动节点更高效;

  • 架构采用monorepo 的方式,分层清晰,同时把编译部分也进行了一些拆解;

  • Vue3 对编译的内容进行了重写,template -- render 函数

    • vue2基于正则,vue3基于状态机 --[ast 编译原理]
    • patchFlag,标记哪些元素包含哪些元素
    • 静态提升
  • vue3 使用了 blockTree,对比需要改变的,优化性能,如果要用jsx的写法就不用优化,不过可以自己去标记

  • ts 重构

  • compiler 拆成了4个包。方便重写

  • vue2 option API -- vue3 composition API

  • vue3是用来 rollup 打包 支持treeShaking


Vue3

    function isObject(data){
        return data && rtpeof data === 'object';
    }
    
    let targetMap = new WeakMap();
    
    //在Vue2里有一个全局变量-Dep.target放watcher,所以还需要有一个全局变量来存放这么个东西。effect副作用
    
    let activeEffect;
    
    //依赖的收集
    function track(target, key){
        let depsMap = targetMap.get(target);
        if(!depsMap) targetMap.set(target,(depsMap = new Map()));
        //再判断 depsMap中有没有key
        let dep = depsMap.get(key);
        if(!dep) depsMap.set(key, (dep = new Set()));
        trackEffect(dep)
    }
    
    function trackEffect(){
        if(!dep.has(activeEffect)) dep.add(activeEffect); //Dep.target && dep.add(Dep.target)
    }
    
    //触发
    function trigger(targrt, key){
        const depsMap = targetMap.get(target);
        if(!depsMap)  return ;
        //effect有一个run方法
        depsMap.get(key).forEach(effect => effect && effect.run())
    }
    
    export function rective(data){
        if(!isObject(data)) return;
        return new Proxy(data, {
            get(target, key, receiver){
                const ret = Reflect.get(target, key, receiver);
                track(target, key);
                return isObject(ret) ? rective(ret): ret;
                set(target, key, value, receiver){
                    Reflect.set(target, key, value, receiver);
                    trigger(target, key)
                    return true;
                },
                deleteProperty(target, key){
                    const ret = Reflect.deleteProperty(target, key);
                    trigger(target, key)
                    return ret;
                },
                has(target, key){
                track(target, key);
                    const ret = Reflect.has(target, key);
                },
                ownKeys(target){
                track(target, key);
                    return Reflect.ownKeys(target);
                }
            }
        })
    }
    
    // const num = ref(0);
    // num.value=xxx
    export function ref(init){
        class RefImpl{
            constructor(init){
                this.__value = init;
            }
            get value(){
                track(this, 'value');
                return this.__value;
            }
            set value(newVal){
                this.__value = newVal;
                trigger(this, 'value');
            }
        }
        return new RefImpl(init)
    }
    
    //定义一个effect 的函数中第一个参数是个函数
    //如果这个函数中有使用ref/reactive
    function effect(fn, options = {}){
        let __effect = new ReactiveEffect(fn);
        
        if(!options.lazy){
            __effect.run()
        }
        return __effect;
    }
    
    export function computed(fn){
        //只考虑函数情况
        let __computed;
        const e = effect(fn,{lazy:true});
        __computed = {
            get value(){
                return e.run();
            }
        }
    }
    
    export function mount(instance, el){
        effect(function(){
            instance.$data && update(linstance , el);
        })
        
        instance.$data = instance.setup();
        update(instance, el);
        function update(instance, el){
            el.innerHTML = instance.render()
        }
    }
    
    class ReactiveEffect {
        constructor(fn){
            this.fn = fn;
        }
        
        run(){
            activeEffect = this;
            return this.fn();
        }
    }