Vue3.0 源码预览

1,489 阅读11分钟

Vue3.0预览版发布

Vue3.0预览版源码已经发布 可以直接看 github源码。 github 搜索 vue-next

vue-next 目前不支持IE11

解决的问题

  • Vue 3 使用ts实现了类型推断,新版api全部采用普通函数,在编写代码时可以享受完整的类型推断(避免使用装饰器)
  • 解决了多组件间逻辑重用问题 (解决:高阶组件、mixin、作用域插槽)
  • Composition API 使用简单

先看看效果

<div id="container"></div>
<script src="vue.global.js"></script>
<script>
    function usePosition(){ 
        let state = Vue.reactive({x:0,y:0});
        function update(e) {
            state.x= e.pageX
            state.y = e.pageY
        }
        Vue.onMounted(() => {
            window.addEventListener('mousemove', update)
        })
        Vue.onUnmounted(() => {
            window.removeEventListener('mousemove', update)
        })
        return Vue.toRefs(state);
    }
    const App = {
        setup(){ // Composition API 使用的入口,相当于v2的created
            const state  = Vue.reactive({name:'666'}); // 定义响应数据
            const {x,y} = usePosition(); // 使用公共逻辑,绑定数据
            Vue.onMounted(()=>{
                console.log('当组挂载完成')
            });
            Vue.onUpdated(()=>{
                console.log('数据发生更新')
            });
            Vue.onUnmounted(()=>{
                console.log('组件将要卸载')
            })
            function changeName(){
                // 更改数据
                state.name = '123';
            }
            return { // 返回上下文,可以在模板中使用
                state,
                changeName,
                x,
                y
            }
        },
        template:`
            <button @click="changeName">{{state.name}} 鼠标x: {{x}} 鼠标: {{y}}</button>
        `
    }
    // 挂载vue实例   react的风格
    Vue.createApp().mount(App,container);
</script>

目录分析

在vue-next目录中,packages目录中包含着Vue3.0所有功能

  • compiler

      compiler-core主要功能是暴露编译相关的API以及baseCompile方法
      compiler-dom基于compiler-core封装针对浏览器的compiler (对浏览器标签进行处理)
    
  • runtime

      runtime-core 虚拟 DOM 渲染器、Vue 组件和 Vue 的各种API
      runtime-test将DOM结构格式化成对象,方便测试
      runtime-dom 基于runtime-core编写的浏览器的runtime (增加了节点的增删改查,样式处理等),返回render、createApp方法
    
  • reactivity

      单独的数据响应式系统,核心方法reactive、effect、 ref、computed
    
  • vue

      整合 compiler + runtime
    
  • 在目录中官方给出了基础的响应式数据的测试文件,我们可以来分析实现原理

const app = {
    template:`<div>{{count}}</div>`,
    data(){
        return {count:100}
    },
}
let proxy = Vue.createApp().mount(app,container);
setTimeout(()=>{
    proxy.count = 200;
},2000)

vue3和vue2 数据响应式的区别

vue2 数据响应

对象拦截,给对象的属性增加set 和 get方法,
因为核心是defineProperty所以还需要对数组的方法进行拦截,
自定义实现数组的方法,通过call来对数组原型方法劫持更改
// 获得原始数组的原型,和它上面的方法  
let oldProtoMehtods = Array.prototype;
let proto = Object.create(oldProtoMehtods);
['push','pop','shift','unshift'].forEach(method=>{
    Object.defineProperty(proto,method,{
        get(){
            update();
            // 方法劫持
            oldProtoMehtods[method].call(this,...arguments)
        }
    })
})


function observer(target){
    // 如果不是对象数据类型直接返回即可
    if(typeof target !== 'object'){
        return target
    }

    if(Array.isArray(target)){
        // 吧劫持后的数组原型给当前data
        Object.setPrototypeOf(target,proto);
        // 给数组中的每一项进行observr
        for(let i = 0 ; i < target.length;i++){
            observer(target[i])
        }
        // 是数组  不走下面的对象处理了
        return
    };
    // 重新定义key
    for(let key in target){
        defineReactive(target,key,target[key])
    }


}
function update(){
    console.log('update view')
}
function defineReactive(obj,key,value){
    observer(value); // 有可能对象类型是多层,递归劫持
    Object.defineProperty(obj,key,{
        get(){
            // 在get 方法中收集依赖,发布订阅模式
            return value
        },
        set(newVal){
            if(newVal !== value){
                // 更改的时候也要触发劫持
                observer(value);
                update(); // 在set方法中触发更新
            }
        }
    })
}
let obj = {name:'666'}
observer(obj);
obj.name = '123';

可以看到

  • Object.defineProperty缺点

    • 无法监听数组的变化
    • 默认深度遍历,浪费内存

vue3 数据响应

vue3使用了ES6中的 Proxy、Reflect;

还有ES6中为我们提供的 Map、Set两种数据结构;

以及其他的一些东西

先用一下

let p = Vue.reactive({name:'666'});
Vue.effect(()=>{ // effect方法会立即被触发,里面的函数会被执行一次
    console.log(p.name);
})
p.name = '123'; // 修改属性后会再次触发effect方法,让里面的函数在执行一次
  • reactive实现
const toProxy = new WeakMap(); // 存放被代理过的对象
const toRaw = new WeakMap(); // 存放已经代理过的对象

function reactive(target){
    // 创建响应式对象
    return createReactiveObject(target);
}
function isObject(target) {
  return typeof target === "object" && target !== null;
}
function hasOwn(target,key){
  return target.hasOwnProperty(key);
}
function createReactiveObject(target){
    // 判断target是不是对象,不是对象不必继续
    if(!isObject(target)){
        return target;
    }

    // 获取里面是否存在
    let observed = toProxy.get(target);
    if(observed){ // 判断是否被代理过
        return observed;
    }
    if(toRaw.has(target)){ // 判断是否要重复代理
        return target;
    }

    // Reflect 是一个es6内置的对象,它提供拦截 JavaScript 操作的方法,和proxy搭配使用,具有返回值
    const handlers = {
        get(target,key,receiver){ // 取值
            console.log('获取')
            let res = Reflect.get(target,key,receiver);
            // 方便取值不用.value  这种方式
            if(res._isRef){
                return res.value
            }
            track(target,'get',key); // 依赖收集
            // 懒代理,只有当取值时再次做代理,vue2.0中一上来就会全部递归增加getter,setter
            return isObject(res) ? reactive(res) : res;
        },
        set(target,key,value,receiver){ // 更改 、 新增属性
           // 更改、新增属性
            let oldValue = target[key]; // 获取上次的值
            let hadKey = hasOwn(target,key); // 看这个属性是否存在
            let result = Reflect.set(target, key, value, receiver);
            if(!hadKey){ // 新增属性
                console.log('更新 添加')
                trigger(target,'add',key); // 触发添加
            }else if(oldValue !== value){ // 修改存在的属性
                console.log('更新 修改')
                trigger(target,'set',key); // 触发修改
            }
            // 当调用push 方法第一次修改时数组长度已经发生变化
            // 如果这次的值和上次的值一样则不触发更新
            return result;
        },
        deleteProperty(target,key){ // 删除属性
            console.log('删除')
            const result = Reflect.deleteProperty(target,key);
            return result;
        }
    }
    // 开始代理
    observed = new Proxy(target,handlers);
    toProxy.set(target,observed); //吧要代理的对象放进去 存放被代理过的对象
    toRaw.set(observed,target); // 做映射表 存放已经代理过的对象,进来的时候直接取用
    return observed;
}
let p = reactive({name:'666'});
console.log(p.name); // 获取
p.name = '165'; // 设置
delete p.name; // 删除
剩下的就是在get和set的时候实现依赖收集和释放,
track的作用是依赖收集,收集的主要是effect里面的方法,
我们先来实现effect原理,之后再完善 track和trigger方法
  • effect

    • effect意思是副作用,此方法默认会先执行一次。
    • 如果数据变化后会再次触发此回调函数。
    function effect(fn) {
        // 创建响应式的effect,返回一个函数来执行,
        // 所以他下面是一个高阶函数
        const effect = createReactiveEffect(fn); 
        // 针对computed  做修改
        // 如果是lazy 则不立即执行, 缓存起来,在调用的时候执行
        if(!options.lazy){ 
            effect();
        }
        // 再返回这个函数 方便再次调用
        return effect;
    }
    const activeReactiveEffectStack = []; // 存放响应式effect
    function createReactiveEffect(fn) {
        // 响应式的effect,高阶函数
        const effect = function() {
            return run(effect, fn);
        };
        // 吧方法设置给effect,后面来判断
        effect.scheduler = options.scheduler;
        return effect;
    }
    // 具体实现
    function run(effect, fn) {
        try {
            activeReactiveEffectStack.push(effect);
            // 先让fn执行,执行时会触发get方法,
            // 可以将effect存入对应的key属性
            return fn(); 
        } finally {
            // 可能不是一个函数,会失败但是最终都会被清理掉 pop
            activeReactiveEffectStack.pop(effect);
        }
    }
  • 当调用fn()时可能会触发get方法,此时会触发track
  • 也可能不会触发,那么久不会执行effect,没有意义
const targetMap = new WeakMap();
function track(target,type,key){
    // 查看是否有effect,获取数组最后一项
    const effect = activeReactiveEffectStack[activeReactiveEffectStack.length-1];
    if(effect){
        // 看这个对象里面是否存在传进来的对象
        let depsMap = targetMap.get(target);
        if(!depsMap){ // 不存在map
            targetMap.set(target,depsMap = new Map());
        }
        // 在这个对象里面再去取值,这次放入set数组
        let dep = depsMap.get(target);
        if(!dep){ // 不存在set
            depsMap.set(key,(dep = new Set()));
        }
        if(!dep.has(effect)){
            dep.add(effect); // 将effect添加到依赖中
        }
    }
}
// 当更新属性时会触发trigger执行,找到对应的存储集合拿出effect依次执行
function trigger(target,type,key){
    const depsMap = targetMap.get(target);
    if(!depsMap){
        return
    }
    let effects = depsMap.get(key);
    if(effects){
        effects.forEach(effect=>{
            // 针对computed实现
            // 如果有scheduler 说明不需要执行effect
            if(effect.scheduler){ 
                // 将dirty设置为true,下次获取值时重新执行runner方法
                effect.scheduler(); 
            }else{
                // 否则就是effect 正常执行即可
                effect(); 
            }
        })
    }
    // 处理如果当前类型是增加属性,如果用到数组的length的effect应该也会被执行
    if (type === "add") {
        let effects = depsMap.get("length");
        if (effects) {
            effects.forEach(effect => {
                effect();
            });
        }
    }

}


vue3的响应式核心已经被实现,有兴趣的可以去查看源码 reactive

ref和computed的实现

  1. ref可以将原始数据类型也转换成响应式数据, 需要通过.value属性进行获取值
function convert(val) {
  return isObject(val) ? reactive(val) : val;
}
function ref(raw) {
  raw = convert(raw);
  const v = {
    _isRef:true, // 标识是ref类型
    get value() {
      track(v, "get", "");
      return raw;
    },
    set value(newVal) {
      raw = newVal;
      trigger(v,'set','');
    }
  };
  return v;
}

// 这样修改之后,在上面的get()就要加上.value了  
get(){
    // ...
    let res = Reflect.get(target, key, receiver);
    if(res._isRef){
        return res.value
    }
    // ...
}
  1. computed 实现也是基于 effect 来实现的, 特点是computed中的函数不会立即执行,多次取值是有缓存机制
  • 用法
    let a = reactive({name:'666'});
    let c = computed(()=>{
        console.log('执行次数')
        return a.name +'123';
    })
    // 不取不执行,取n次只执行一次
    console.log(c.value);
    console.log(c.value);
  • 实现
function computed(getter){
  let dirty = true;
  const runner = effect(getter,
    // 标识这个effect是懒执行
    { 
        // 懒执行
        lazy:true, 
        // 当依赖的属性变化了,调用此方法,而不是重新执行effect
        scheduler:()=>{ 
            dirty = true;
        }
    }
  );
  let value;
  return {
    _isRef:true,
    get value(){
      if(dirty){
        value = runner(); // 执行runner会继续收集依赖
        dirty = false;
      }
      return value;
    }
  }
}

// 同时  我们需要对effect trigger  都做修改

完整代码,可以被引入直接用于数据双向绑定的reactive方法


const toProxy = new WeakMap(); // 存放被代理过的对象
const toRaw = new WeakMap(); // 存放已经代理过的对象

function reactive(target){
    // 创建响应式对象
    return createReactiveObject(target);
}
function isObject(target) {
  return typeof target === "object" && target !== null;
}
function hasOwn(target,key){
  return target.hasOwnProperty(key);
}
function createReactiveObject(target){
    // 判断target是不是对象,不是对象不必继续
    if(!isObject(target)){
        return target;
    }

    // 获取里面是否存在
    let observed = toProxy.get(target);
    if(observed){ // 判断是否被代理过
        return observed;
    }
    if(toRaw.has(target)){ // 判断是否要重复代理
        return target;
    }

    // Reflect 是一个es6内置的对象,它提供拦截 JavaScript 操作的方法,和proxy搭配使用,具有返回值
    const handlers = {
        get(target,key,receiver){ // 取值
            console.log('获取')
            let res = Reflect.get(target,key,receiver);
            // 方便取值不用.value  这种方式
            if(res._isRef){
                return res.value
            }
            track(target,'get',key); // 依赖收集
            // 懒代理,只有当取值时再次做代理,vue2.0中一上来就会全部递归增加getter,setter
            return isObject(res) ? reactive(res) : res;
        },
        set(target,key,value,receiver){ // 更改 、 新增属性
           // 更改、新增属性
            let oldValue = target[key]; // 获取上次的值
            let hadKey = hasOwn(target,key); // 看这个属性是否存在
            let result = Reflect.set(target, key, value, receiver);
            if(!hadKey){ // 新增属性
                console.log('更新 添加')
                trigger(target,'add',key); // 触发添加
            }else if(oldValue !== value){ // 修改存在的属性
                console.log('更新 修改')
                trigger(target,'set',key); // 触发修改
            }
            // 当调用push 方法第一次修改时数组长度已经发生变化
            // 如果这次的值和上次的值一样则不触发更新
            return result;
        },
        deleteProperty(target,key){ // 删除属性
            console.log('删除')
            const result = Reflect.deleteProperty(target,key);
            return result;
        }
    }
    // 开始代理
    observed = new Proxy(target,handlers);
    toProxy.set(target,observed); //吧要代理的对象放进去 存放被代理过的对象
    toRaw.set(observed,target); // 做映射表 存放已经代理过的对象,进来的时候直接取用
    return observed;
}
 function effect(fn) {
        // 创建响应式的effect,返回一个函数来执行,
        // 所以他下面是一个高阶函数
        const effect = createReactiveEffect(fn); 
        // 针对computed  做修改
        // 如果是lazy 则不立即执行, 缓存起来,在调用的时候执行
        if(!options.lazy){ 
            effect();
        }
        // 再返回这个函数 方便再次调用
        return effect;
    }
    const activeReactiveEffectStack = []; // 存放响应式effect
    function createReactiveEffect(fn) {
        // 响应式的effect,高阶函数
        const effect = function() {
            return run(effect, fn);
        };
        // 吧方法设置给effect,后面来判断
        effect.scheduler = options.scheduler;
        return effect;
    }
    // 具体实现
    function run(effect, fn) {
        try {
            activeReactiveEffectStack.push(effect);
            // 先让fn执行,执行时会触发get方法,
            // 可以将effect存入对应的key属性
            return fn(); 
        } finally {
            // 可能不是一个函数,会失败但是最终都会被清理掉 pop
            activeReactiveEffectStack.pop(effect);
        }
    }

    const targetMap = new WeakMap();

    function track(target,type,key){
        // 查看是否有effect,获取数组最后一项
        const effect = activeReactiveEffectStack[activeReactiveEffectStack.length-1];
        if(effect){
            // 看这个对象里面是否存在传进来的对象
            let depsMap = targetMap.get(target);
            if(!depsMap){ // 不存在map
                targetMap.set(target,depsMap = new Map());
            }
            // 在这个对象里面再去取值,这次放入set数组
            let dep = depsMap.get(target);
            if(!dep){ // 不存在set
                depsMap.set(key,(dep = new Set()));
            }
            if(!dep.has(effect)){
                dep.add(effect); // 将effect添加到依赖中
            }
        }
    }
    // 当更新属性时会触发trigger执行,找到对应的存储集合拿出effect依次执行
    function trigger(target,type,key){
        const depsMap = targetMap.get(target);
        if(!depsMap){
            return
        }
        let effects = depsMap.get(key);
        if(effects){
            effects.forEach(effect=>{
                // 针对computed实现
                // 如果有scheduler 说明不需要执行effect
                if(effect.scheduler){ 
                    // 将dirty设置为true,下次获取值时重新执行runner方法
                    effect.scheduler(); 
                }else{
                    // 否则就是effect 正常执行即可
                    effect(); 
                }
            })
        }
        // 处理如果当前类型是增加属性,如果用到数组的length的effect应该也会被执行
        if (type === "add") {
            let effects = depsMap.get("length");
            if (effects) {
                effects.forEach(effect => {
                    effect();
                });
            }
        }

    }

    function convert(val) {
        return isObject(val) ? reactive(val) : val;
    }
    function ref(raw) {
        raw = convert(raw);
        const v = {
            _isRef:true, // 标识是ref类型
            get value() {
            track(v, "get", "");
            return raw;
            },
            set value(newVal) {
            raw = newVal;
            trigger(v,'set','');
            }
        };
        return v;
    }

    function computed(getter){
        let dirty = true;
        const runner = effect(getter,
            // 标识这个effect是懒执行
            { 
                // 懒执行
                lazy:true, 
                // 当依赖的属性变化了,调用此方法,而不是重新执行effect
                scheduler:()=>{ 
                    dirty = true;
                }
            }
        );
        let value;
        return {
            _isRef:true,
            get value(){
            if(dirty){
                value = runner(); // 执行runner会继续收集依赖
                dirty = false;
            }
            return value;
            }
        }
    }