Vue3源码学习——响应式原理

495 阅读8分钟

Vue3响应式原理

「时光不负,创作不停,本文正在参加2021年终总结征文大赛

前言

前端菜鸟在学习Vue3时的总结输出,若有错误或者不足的地方,还请各位大佬们多多指正,不吝赐教。

本文主要对于Vue3响应式原理的总结,在进行响应式原理学习之前,首先要弄清出什么是响应式。

什么是响应式

有一个初始化的值,有一段代码使用了这个值,当这个值发生变化时,这段代码也自动重新执行,这种可以自动响应数据变化的代码机制,称为响应式 我们看一段代码:

   let a = 10
   let b = a + 10
   console.log(b)  // b=20
     
   a = 20
     
   //预期达到的结果b = 30
   console.log(b)   // 实际b = 20

想让变量 b 跟变量a的变化发生变化,我们应该怎么做呢?

  1.   let a = 10
      let b = a + 10
      console.log(b)  // b=20
      a = 20
      
      //在对b变量进行一次赋值操作
      b = a + 10
      console.log(b)  // b = 30
    
  2. let a = 10
    let b 
    update()  // b=20
    a = 20
    //封装一个update函数
    function update(){
        return b = a + 10
        console,log(b)
    }    
    update()  //b=30
    
  3. 变量a发生了变化,我希望变量b可以自动发生变化,这个时候我们可以安装vue的响应式模块(注:Vue库被拆分多个模块,每个模块可以单独下载使用)

    npm install @vue/reactivity

       const {reactive,effect}  = require("@vue/reactivity")
       //首先声明一个响应式变量a
       let a = reactive({
           value:1
       })
       let b
       //监听
       effect(()=>{
           //传入一个函数作为参数
           //会先执行一次这个函数
           b = a.value + 10
           console.log(b
        })
        //a的响应式对象的值发生改变后 effect 会在执行一次
        a.value = 10
    
    

上面第三种方法就是Vue的响应式方法,下面我们就尝试自己实现应式的方法

Vue响应式的实现

定义一个响应式函数

实现响应式的一个关键就是响应式函数watchEffect的实现,凡是传入watchEffect的函数,就是需要响应式的,其他默认定义的函数时不需要响应式的

    const effects = new Set() //用来收集需要函数
    function watchEffect(effect){
        effects.push(effect)
        effect()  //执行需要响应式的函数
     }  

响应式依赖的收集

目前我们收集的依赖是放到一个数组中来保存,但是这会存在数据管理的问题

  • 我们在实际开发中需要监听的是很多对象的响应式
  • 这些对象需要监听的不只是一个属性,他们很多属性的变化,都会有对应的响应式函数
  • 我们不可能在全局维护一大堆的数组来保存这些相应是函数

所以我们要设计一个类,用这个类来管路某一个对象的某一个属性的所有响应式函数,相当于代替了原来简单的effects的数组

class  Depend {
      constructor(){
          this.effects = new Set() //依赖不能重复
      }
      // 收集依赖
     depend(effect)
         this.effects.add(Effect)
     }  
     //响应依赖
     notify(){
         this.effects.forEach(effect=>{
             effect()
          }
     }   

怎样将收集响应式依赖的函数和管理依赖的类串联起来

接下来我们考虑一个问题 如何将响应式响应式函数添加到我们定义的类中间呢?我们可以在全局定义一个中间变量currentEffect进行传值,代码优化如下:

let currentEffect = null
// 依赖的类
class  Depend {
       constructor(){
           this.effects = new Set() //依赖不能重复
       }
       // 收集依赖
      depend(){
           if (currentEffect) {
                this.effects.add(currentEffect);
            }
      }  
      //响应依赖
      notify(){
          this.effects.forEach(effect=>{
              effect()
           })
      }  
}
//收集响应式依赖的函数
function watchEffect(effect){
    currentEffect = effect
    effect()
    currentEffect = null
}

vue3 ref的简单实现

接下来就可以简单实现类似ref变量

let currentEffect = null
// 依赖的类
class  Depend {
       constructor(val){
           this.effects = new Set() //依赖不能重复
           this._val =val
       }
        get value() {
        // 访问对象一个属性  会触发get函数  --在这里面可以自动收集依赖
        return this._val;
    }
    set value(newVal) {
            this._val = newVal;
            // 访问对象一个属性  会触发set函数  --在这里面可以自动触发依赖
        }
       // 收集依赖
      depend(){
           if (currentEffect) {
                this.effects.add(currentEffect);
            }
      }  
      //响应依赖
      notify(){
          this.effects.forEach(effect=>{
              effect()
           })
      }  
}

//定义一个dep
const dep = new Depend(10)
//收集响应式依赖的函数
function watchEffect(effect){
    currentEffect = effect
    effect()
    dep.depend()
    currentEffect = null
}


let b;
watchEffect(() => {
    b = dep.value + 10;
    console.log(b);
});
dep.value = 20
dep.notify() 

运行结果如下

image.png

响应式效果已经出来了,但是响应式函数的收集和响应都是我们手动触发和响应的,我们应该思考能不能自动进行依赖的收集和响应?

我们在dep.value的访问和修改值时候会触发gettersetter函数,所以我们可以在gettersetter函数中进行依赖的收集和响应,代码如下

let currentEffect = null
// 依赖的类
class  Depend {
       constructor(val){
         this.effects = new Set()
         this,_val = val
       }
        get value() {
        // 访问对象一个属性  会触发get函数  --在这里面可以自动收集依赖
        this.depend()
        return this._val;
    }
    set value(newVal) {
            this._val = newVal;
            // 访问对象一个属性  会触发set函数  --在这里面可以自动触发依赖
            this.notify()
        }
       // 收集依赖
      depend(){
          this.effects.add(currentEffect)
      }  
      //响应依赖
      notify(){
          this.effects.forEach(effect=>{
              effect()
           })
      }  
}

//定义一个dep
const dep = new Depend(10)
//收集响应式依赖的函数
function watchEffect(effect){
    currentEffect = effect
    effect()
    currentEffect = null
}


let b;
watchEffect(() => {
    b = dep.value + 10;
    console.log(b);
});

reactive的简单实现

目前我们是创建一个dep对象,用来管理对于一个简单数据变化需要建统的响应函数,但实际开发中我们会有不同的对象,另外会有不同的属性需要管理,所以我们需要一中数据结构来管理不同的对象的不同依赖

image.png

我们可以写一个getDepend函数专门管理这种依赖关系

 const targetMap = new WeakMap()  //存储所有
 function getDepend(traget,key){
     let objMap = targetMap.get(target)
     if(!objMap) {
         objMap = new Map()
         targetMap.set(target,objMap)
     }
     let dep = objMap.get(key)
     if(!dep){
         dep = new Dep()
         objMap.set(key,dep)
   }     

接下来我们需要监听对象的变化

function reactive(obj){
        return new Proxy(obj,{
            get(target,key,recriver)
                cosnt dep = getDepend(target,key)  //获取依赖
                dep.edpend()  //收集依赖
                return Reflect.get(target,key,receiver)
             }
             set(target,key,newVal,receiver){
                
                 cosnt dep = getDepend(target,key)  //获取依赖
                 Reflect.set(target,key,newVal,receiver)
                 dep.notify()  //触发依赖要在reflect之后  在之前触发还没来得及修改值
                 
            }
 }

综合代码并举例

let currentEffect = null
// 依赖的类
class  Depend {
       constructor(){
         this.effects = new Set()
       }
       // 收集依赖
      depend(){
           if (currentEffect) {
                this.effects.add(currentEffect);
            }
      }  
      //响应依赖
      notify(){
          this.effects.forEach(effect=>{
              effect()
           })
      }  
}
//收集依赖的函数
function watchEffect(effect){
    currentEffect = effect
    effect()
    currentEffect = null
}
// 封装的获取依赖函数
 const targetMap = new WeakMap()  //存储所有
 function getDepend(traget,key){
     let objMap = targetMap.get(target)
     if(!objMap) {
         objMap = new Map()
         targetMap.set(target,objMap)
     }
     let dep = objMap.get(key)
     if(!dep){
         dep = new Depend()
         objMap.set(key,dep)
    }
    return dep
 } 
  //创建响应式对象函数
  function reactive(obj){
        return new Proxy(obj,{
            get(target,key,receiver)
                const dep = getDepend(target, key) //获取依赖
                dep.depend()  //收集依赖
                return Reflect.get(target,key,receiver)
             }
             set(target,key,newVal,receiver){
                
                 const dep = getDepend(target, key) //获取依赖
                 Reflect.set(target,key,newVal,receiver)
                 dep.notify()  //触发依赖要在reflect之后  在之前触发还没来得及修改值
                 
            })
 }
 var user = reactive({
    age: 19,
});
let double;

watchEffect(() => {
    console.log("-----reactive-----");
    double = user.age * 2;
    console.log(double);
});
user.age = 20;

结果如下图

image.png

代码总结

//ref->
let currentEffect = null
// 依赖的类
class  Depend {
       constructor(val){
         this.effects = new Set()
         this,_val = val
       }
        get value() {
        // 访问对象一个属性  会触发get函数  --在这里面可以自动收集依赖
        this.depend()
        return this._val;
    }
    set value(newVal) {
            this._val = newVal;
            // 访问对象一个属性  会触发set函数  --在这里面可以自动触发依赖
            this.notify()
        }
       // 收集依赖
      depend(){
          this.effects.add(currentEffect)
      }  
      //响应依赖
      notify(){
          this.effects.forEach(effect=>{
              effect()
           })
      }  
}

//定义一个dep
const dep = new Depend(10)
//收集响应式依赖的函数
function watchEffect(effect){
    currentEffect = effect
    effect()
    currentEffect = null
}


let b;
watchEffect(() => {
    b = dep.value + 10;
    console.log(b);
});

//reatctive->
let currentEffect = null
// 依赖的类
class  Depend {
       constructor(){
         this.effects = new Set()
       }
       // 收集依赖
      depend(){
           if (currentEffect) {
                this.effects.add(currentEffect);
            }
      }  
      //响应依赖
      notify(){
          this.effects.forEach(effect=>{
              effect()
           })
      }  
}
//收集依赖的函数
function watchEffect(effect){
    currentEffect = effect
    effect()
    currentEffect = null
}
// 封装的获取依赖函数
 const targetMap = new WeakMap()  //存储所有
 function getDepend(traget,key){
     let objMap = targetMap.get(target)
     if(!objMap) {
         objMap = new Map()
         targetMap.set(target,objMap)
     }
     let dep = objMap.get(key)
     if(!dep){
         dep = new Depend()
         objMap.set(key,dep)
    }
    return dep
 } 
  //创建响应式对象函数
  function reactive(obj){
        return new Proxy(obj,{
            get(target,key,receiver)
                const dep = getDepend(target, key) //获取依赖
                dep.depend()  //收集依赖
                return Reflect.get(target,key,receiver)
             }
             set(target,key,newVal,receiver){
                
                 const dep = getDepend(target, key) //获取依赖
                 Reflect.set(target,key,newVal,receiver)
                 dep.notify()  //触发依赖要在reflect之后  在之前触发还没来得及修改值
                 
            })
 }
 //reactive 举例
 var user = reactive({
    age: 19,
});
let double;

watchEffect(() => {
    console.log("-----reactive-----");
    double = user.age * 2;
    console.log(double);
});
user.age = 20;

vue2的实现

let currentEffect = null
// 依赖的类
class  Depend {
       constructor(){
         this.effects = new Set()
       }
       // 收集依赖
      depend(){
           if (currentEffect) {
                this.effects.add(currentEffect);
            }
      }  
      //响应依赖
      notify(){
          this.effects.forEach(effect=>{
              effect()
           })
      }  
}
//收集依赖的函数
function watchEffect(effect){
    currentEffect = effect
    effect()
    currentEffect = null
}
// 封装的获取依赖函数
 const targetMap = new WeakMap()  //存储所有
 function getDepend(traget,key){
     let objMap = targetMap.get(target)
     if(!objMap) {
         objMap = new Map()
         targetMap.set(target,objMap)
     }
     let dep = objMap.get(key)
     if(!dep){
         dep = new Depend()
         objMap.set(key,dep)
    }
    return dep
 } 
  //创建响应式对象函数
  function reactive(obj){
       Object.keys(obj).forEach(key=>{
           let value = obj[key]
           Object.defineProperty(obj, key, {
               get(){
                   const dep = getDepend(obj,key)
                   dep.depend()
                   return value
                },
                set(newVal){
                   value = newVal
                   const dep = getDepend(obj,key)
                   dep.notify()
                }
      return obj
  }
  

小结

本文的输出立足于b站up主阿崔cxr的开源项目mini-vue,有兴趣的小伙伴可以去了解下😁😁😁😁