目录

  • 准备
  • vue3 reactive原理例子重点讲解
  • vue3 reactive原理例子完整代码

准备:

  1. 下载vue-next

  2. 安装依赖 npm install

核心部分package,里面的vue整合package内各个文件,__tests__里面的单元测试文件(以spect.ts结尾)用来测试功能是否符合预期的

  1. 启动开发环境 npm run dev,默认将代码打包到dist,会把ts编译成js

  2. 引用编译出来的文件即可进行测试vue3.0

在这里插入图片描述

packages里面的文件说明

  • compiler- 用于编译

  • reactivity- 用于响应式

  • runtime- 代码运行时的方法

  • server-randerer 服务器端渲染

  • shared 所有包中的共享方法

-core是核心,-dom是基于core封装针对浏览器的

没有对比没有伤害,复习:vue2响应式原理

vue2响应式原理:核心使用Object.defineProperty给属性定义get和set

vue3 reactive的基本使用

let proxy = Vue.reactive({name:'fur'})
//effect:副作用,默认会调用一次,数据变化时触发
    Vue.effect(()=>{
    console.log(proxy.name)
})
proxy.name = 'furfur-jiang'
//fur
//furfur-jiang

vue3 reactive原理例子

先分开逐个重点讲解,再整合起来

划重点部分:弱映射表,多层递归,Reflect,数组,依赖收集
弱映射表

需要用到弱引用映射表记录一下, 防止对象已经代理过了或者多次代理同一个对象,即防止多次出现 reactive(object) 与 reactive(proxy);弱引用映射表(es6),使用get和set方法进行存取

弱映射表定义

let toProxy = new WeakMap()//原对象=>被代理对象
let toRaw = new WeakMap()//被代理对象=>原对象

下面代码位于createReactive方法中,因为在createReactive会new observed,目的就是为了避免多次new observed

if(toProxy.get(target)){
        return proxy
}
//如果对象再次被代理,返回原对象
//即判断该对象已经是代理过的,不再次代理
//不需要使用get即不需要获取,直接判断target有无代理过既可
if(toRaw.has(target)){
  return target
}
多层递归

关键在于isObject(res)?reactive(res):res;若res为对象,需要递归,与vue2一上来就递归不同,会判断需要递归再递归,

get(target,key,receiver){
    console.log("获取")
    let res = Reflect.get(target,key,receiver)
    return isObject(res)?reactive(res):res;
    // return target[key] 效果等同Reflect.get方法
}
Reflect

observed的参数baseHandler对象,Reflect.get,Reflect.set,Reflect.deleteProperty

reflect 优点:不会报错,而且有返回值,会替代掉Object上的方法

let baseHandler = {
//receiver表示代理后对象
get(target,key,receiver){
    console.log("获取")
    let res = Reflect.get(target,key,receiver)
    return isObject(res)?reactive(res):res;
    //实现多层代理,若res为对象,需要递归
    // return target[key] 效果等同Reflect.get方法
}, 
set(target,key,value,receiver){
    console.log("设置")
    let res = Reflect.set(target,key,value,receiver)
    return res
    //target[key] = value 
    //效果等同Reflect.set方法,但是上面会有布尔类型返回值,明确设置成功或者失败
},
deleteProperty(target,key){
    console.log("删除")
    return Reflect.deleteProperty(target,key)
    }
}
let observed = new Proxy(target,baseHandler)
数组

存在问题,因为会涉及length修改,所以如果不屏蔽,会触发两次,即两次视图更新,不需要

即第一次将元素 push 进去,第二次将 length 改成 对应的值,但是第二次是无意义的更新,需要屏蔽

关键:判断是否为新增属性

function hasOwn(target,key){
    return target.hasOwnProperty(key)
}

在set中进行处理

set(target,key,value,receiver){
    let hadKey = hasOwn(target,key)
    let oldValue = target[key]
    let res = Reflect.set(target,key,value,receiver)
    //判断是否新增属性
    if(!hadKey){
    	console.log('新增属性')
    }else if(oldValue !== value){
        //如果修改的不等于原来的,才会执行,即需要手动改length才会执行
        console.log('修改属性')
    }
    return res
    //target[key] = value 
    //效果等同Reflect.set方法,但是上面会有布尔类型返回值,明确设置成功或者失败
}

调用:

let arr = [1,2,3]
let proxyArr = reactive(arr)
proxyArr.push(4)  //新增属性
proxyArr.length = 5 //修改属性
依赖收集

activeEffectStacks 栈:先进后出,目的让属性和方法关联,形成响应

effect方法:响应式副作用,默认先执行一次,依赖数据变了再执行

createReactiveEffect方法:创建响应式的副作用

run方法:1.让fn执行, 2.将effect存入栈

//响应式副作用
function effect(fn){
    //createReactiveEffect:创建响应式的副作用
    let effect = createReactiveEffect(fn)
    effect()//默认先执行一次
}
function createReactiveEffect(fn){
    let effect = function(){
        return run(effect,fn) 
    }
    return effect;
}
function run(effect,fn){
    //防止由于报错而不继续执行
    try{
        activeEffectStacks.push(effect);
        fn();
    }finally{
        activeEffectStacks.pop()
    }
}

let obj = reactive({name:'fur'})
effect(()=>{
    //运行get时进行依赖收集
    console.log(obj.name)
})

get方法调用(跟踪)track()和set方法调用(触发)trigger()

在这里插入图片描述

function track(target,key){//跟踪
    let effect = activeEffectStacks[activeEffectStacks.length-1]
    if(effect){//有对应关系才关联,动态创建依赖关系
        let depsMap = targetsMap.get(target)
        if(!depsMap){
        targetsMap.set(target,depsMap = new Map)
        }
        let deps = depsMap.get(key)
        if(!deps){
            depsMap.set(key,deps = new Set())
        }
        if(!deps.has(effect)){
            deps.add(effect)
        }
    }
}

function trigger(target,type,key){//触发
    let depsMap = targetsMap.get(target)
    if(depsMap){
        let deps = depsMap.get(key)
        if(deps){
            //将当前key对应的effect一次执行
            deps.forEach(effect=>{
                effect()
            })
        }
    }    
}

完整代码

下面贴出完整代码myreactive.js:包含详细注释

//弱映射表
let toProxy = new WeakMap()//原对象=>被代理对象
let toRaw = new WeakMap()//被代理对象=>原对象

function isObject(val){
   return typeof val === 'object' && val !== null;
}

//判断当前对象上有没有这个属性
function hasOwn(target,key){
    return target.hasOwnProperty(key)
}

function reactive(target){
    return createReactive(target)
}

function createReactive(target){
    //如果已经被代理过了,返回代理结果
    if(toProxy.get(target)){
        return proxy
    }
    //如果对象再次被代理,返回原对象
    //即判断该对象已经是代理过的,不再次代理
    //不需要使用get即不需要获取,直接判断target有无代理过既可
    if(toRaw.has(target)){
        return target
    }

    if(!isObject(target)){
        return target
    }
    let baseHandler = {
        //receiver表示代理后对象
        // reflect 优点:不回报错,而且有返回值,会替代掉Object上的方法
        get(target,key,receiver){
            // console.log("获取")
            let res = Reflect.get(target,key,receiver)

            //收集依赖,把当前key和effect对应起来
            //如果目标上的key变了,重新让数组里的fn执行即可
            track(target,key)


            return isObject(res)?reactive(res):res;
            //实现多层代理,若res为对象,需要递归
            // return target[key] 效果等同Reflect.get方法
        }, 
        set(target,key,value,receiver){
            let hadKey = hasOwn(target,key)
            let oldValue = target[key]
            let res = Reflect.set(target,key,value,receiver)
            //判断是否新增属性
            if(!hadKey){
                trigger(target,'add',key)
                console.log('新增属性')
            }else if(oldValue !== value){
                trigger(target,'set',key)
                //如果修改的不等于原来的,才会执行,即需要手动改length才会执行
                console.log('修改属性')
            }
            console.log("设置")
            return res
            //target[key] = value 
            //效果等同Reflect.set方法,但是上面会有布尔类型返回值,明确设置成功或者失败
        },
        deleteProperty(target,key){
            console.log("删除")
            return Reflect.deleteProperty(target,key)
        }
    }
    let observed = new Proxy(target,baseHandler)

    toProxy.set(target,observed)
    toRaw.set(observed,target)

    return observed
}

let object = {name:'fur',hobby:{play:'code'}}
let proxy = reactive(object)
//需要记录一下, 防止对象已经代理过了或者多次代理同一个对象
//即防止多次出现 reactive(object) 与 reactive(proxy)
//利用弱引用映射表(es6),使用get和set方法进行存取


proxy.name//获取
proxy.name = 'furfur-jiang' //设置 获取
console.log(proxy.name)//furfur-jiang
delete proxy.name  //删除

//验证实现了多层代理
proxy.hobby.play = 'study'
console.log(proxy.hobby.play)//study


//对数组操作
//存在问题,因为会涉及length修改,所以如果不屏蔽,会触发两次,第一次将4push进去,第二次将length改成4
let arr = [1,2,3]
let proxyArr = reactive(arr)
proxyArr.push(4)    //新增属性
proxyArr.length = 5 //修改属性
console.log(proxyArr)



//依赖收集,也称发布订阅
//栈:先进后出,目的让属性和方法关联,形成响应
let activeEffectStacks = []

let targetsMap = new WeakMap(); // 集合和hash表
function track(target,key){
    let effect = activeEffectStacks[activeEffectStacks.length-1]
    if(effect){//有对应关系才关联,动态创建依赖关系
        let depsMap = targetsMap.get(target)
        if(!depsMap){
        targetsMap.set(target,depsMap = new Map)
        }
        let deps = depsMap.get(key)
        if(!deps){
            depsMap.set(key,deps = new Set())
        }
        if(!deps.has(effect)){
            deps.add(effect)
        }
    }
}

function trigger(target,type,key){
    let depsMap = targetsMap.get(target)
    if(depsMap){
        let deps = depsMap.get(key)
        if(deps){
            //将当前key对应的effect一次执行
            deps.forEach(effect=>{
                effect()
            })
        }
    }
    
}


//响应式副作用
function effect(fn){
    //createReactiveEffect:创建响应式的副作用
    let effect = createReactiveEffect(fn)
    effect()//默认先执行一次
}
function createReactiveEffect(fn){
    let effect = function(){
        return run(effect,fn) //1.让fn执行, 2.将effect存入栈
    }
    return effect;
}
function run(effect,fn){
    //防止由于报错而不继续执行
    try{
        activeEffectStacks.push(effect);
        fn();
    }finally{
        activeEffectStacks.pop()
    }
}

let obj = reactive({name:'fur'})
//effect:响应式副作用,默认先执行一次,依赖数据变了再执行
effect(()=>{
    //运行get,
    console.log(obj.name)
})
obj.name = 'furfur-jiang'
obj.name = 'furfur-jiang'//无意义修改,只运行一次
obj.name = 'furfurJiang'//有意义修改,会运行两次

在这里插入图片描述

Logo

前往低代码交流专区

更多推荐