03.vue3源码学习之依赖收集和触发更新

我正在参加「掘金·启航计划」

1.什么是响应性

这个术语在今天的各种编程讨论中经常出现,但人们说它的时候究竟是想表达什么意思呢?本质上,响应性是一种可以使我们声明式地处理变化的编程范式。

对于js而言

let A0 = 1
let A1 = 2
let A2 = A0 + A1
console.log(A2) // 3

A0 = 2
console.log(A2) // 仍然是 3

当我们更改 A0 后,A2 不会自动更新

那么我们如何在 JavaScript 中做到这一点呢?首先,为了能重新运行计算的代码来更新 A2,我们需要将其包装为一个函数:

let A2




function effect() {
  A2 = A0 + A1
}

然后,我们在定义几个术语

  • 这个 effect() 函数会产生一个副作用,或者就简称为作用 (effect),因为它会更改程序里的状态。
  • A0 和 A1 被视为这个作用的依赖 (dependency),因为它们的值被用来执行这个作用。因此这次作用也可以说是一个它依赖的订阅者 (subscriber)。

所以我们希望有个函数,能够在 A0 A1 (这两个依赖) 变化时调用 effect() (产生作用)

whenDepsChange(update)

我们希望whenDepsChange函数应该有如下功能:

1.在变量被读取的时候进行追踪,比如我们在A0+A1时,我们应该就跟踪到A0和A1

2.当变量在挡在effect()执行是被访问,那effect()需要在第一次调用后,成为A0和A1的订阅者

3.当A0或者A1发生变化后,我们应该通知其effect()函数执行

举个简单的例子:

let A0 = 1


let A1 = 2


const effect = () => {A2 = A1 + A0  }


effect()  //先执行一次
console.log(A2) // 3


A0= 2 
effect() //再执行一次


console.log(A2) // 4

但这样也会存在问题,就是如果存在一个依赖在多处使用的情况,那我们就需要产生多个effect,举个简单的例子

let A0 = 1


let A1 = 2


const effect = () => {A2 = A1 + A0  }


const effect1 = () => {A2 = A1 + A0 + 5  }

effect()  //先执行一次
effect1()  //先执行一次
console.log(A2) // 3
console.log(A2) // 8


A0= 2 
effect() //再执行一次
effect1() //再执行一次



console.log(A2) // 4
console.log(A2) // 9

2.什么是track和trigger

针对于上面的问题,想出来的解决办法就是,用track函数把所有依赖于A2变量的effect函数都收集起来,放在dep中,以后只要A2变量一改变,就执行trigger函数通知dep里面所有的依赖A2变量的函数执行,实现依赖变量的更新,代码演示如下:

let A0 = 1


let A1 = 2


const effect = () => {A2 = A1 + A0  }


const effect1 = () => {A2 = A1 + A0 + 5  }

const dep = new Set()
const track = () => {
  dep.add(effect)
  dep.add(effect1)
}

const trigger = () => {
  dep.forEach(effect => effect())
}
effect()  //先执行一次
effect()  //先执行一次
console.log(A2) // 3
console.log(A2) // 8


A0= 2 
trigger()

console.log(A2) // 4
console.log(A2) // 9

用一张图来说明依赖收集和响应更新的过程

总结:就是收集A0变量,在几个地方使用,就有几个副作用effect,如果后续数据发生变化,那么就通知其所有的副作用effect再次执行

想一想,如果是在一个对象里面怎么办,或者多个对象里面怎么办

举个简单的例子:我们要收集不同的对象下面的属性,做出不同的变更

let person = {
  name:"张三",
  age:18
}
let animal = {
  name:"大黄",
  age:2
}

let personStr
let animalStr
const personEffect = () => { personStr = `名字叫${person.name},年龄${person.age}`}
const animalEffect = () => { animalStr = `名字叫${animal.name},年龄${animal.age}`}



//收集依赖的过程
const targetMap = new WeakMap()
const track = (target,key) => {
  const depsMap = targetMap.get(target)
  if(!depsMap){
    targetMap.set(target,depsMap = new Map())
  }
  
  let dep = depsMap.get(key)
  if(!dep){
    depsMap.set(key, dep = new Set())
  }
  if(target === person){
    if(key ==='name'){
      dep.add(personEffect1)
      dep.add(personEffect2)
    }else if(key ==='age'){
      dep.add(personEffect1)
    }
  }else if(target === animal){
     if(key ==='name'){
      dep.add(animalEffect1)
      dep.add(animalEffect2)
    }else if(key ==='age'){
      dep.add(animalEffect1)
    }
  }
}

personEffect()
animalEffect()
console.log(personStr,animalEffect)
// 名字叫张三,年龄18   名字叫大黄,年龄2


//收集对应对象下面的对应数据的依赖
track(person,'name')
track(person,'age')
track(animal,'name')
track(animal,'age')

//在set的时候执行trigger
function trigger(target, key) {
    let depsMap = targetMap.get(target)
    if (depsMap) {
        const dep = depsMap.get(key)
        if (dep) {
            dep.forEach(effect => effect())
        }
    }
}
person.name = '李四'
persong.age = 25
animal.name = '小黄'
animal.age = 3

trigger(person,'name')
trigger(person,'age')
trigger(animal,'name')
trigger(animal,'age')

console.log(personStr,animalEffect)
// 名字叫李四,年龄25   名字叫小黄,年龄3

一张图,表示多对象收集收集依赖

总结:

  • 一个对象里面,那么对象中那一个属性被用到,就去追踪到那个属性
  • 一个属性如果在多个地方用到,那么就会产生多个effect
  • 最终得出来的结论就是,那一个对象的那一个属性,在哪里调用,就产生一个effect

3.实现track和trigger

  • 明确第一点,就是我们要知道当前执行的副作用函数effect是在收集那个依赖
let state = reactive({name:'ly'},age:12,arr:[1,23])





effect(() => {

   app.innerHTML = state.name;

})

1.依赖收集的实现(track)

1.我们要让effect变成响应式的effect,可以等到数据变化的时候就重新执行

export function effect(fn,options:any={}){
    //我需要让这个effect变成响应式的effect,可以坐到数据变化就重新执行
    const effect = createReactiveEffect(fn,options)
    //默认effect会先执行一次
    effect()
    return effect
}
  1. createReactiveEffect的实现,保证当前的activeEffect存在
let uid = 0
const effectStack = []
let activeEffect; //存储当前的effect
export function createReactiveEffect(fn,options){
    const effect = function reactiveEffect(){
        if(!effectStack.includes(effect)){
            try {
                effectStack.push(effect)
                activeEffect = effect
                return fn() //函数执行时取值, 会执行get方法
            } finally {
                effectStack.pop()
                activeEffect = effectStack[effectStack.length - 1]
            }
        }



    }

    effect.id = uid++ //制作一个effect标识,用于区分effect
    effect._isEffect = true //用于标识这个是响应式的effect
    effect.raw = fn //保留effect对应的原函数
    effect.options = options //在effect上面保存用户的属性
    return effect
}

  1. 在effect函数执行的执行,就会走到get方法中,get方法中会知道是哪个一个对象的,那一个属性,正在收集依赖,保证使那个对象的那个属性,收集到对应的effect
get(target, key, receiver) { // let proxy = reactive({obj:{}})
  const res = Reflect.get(target, key, receiver); // target[key];
  
  if(!isReadonly){
      console.log('执行effect时会取值','收集effect')
      track(target,'get',key)
  }
}

  1. track方法,收集依赖
let targetMap = new WeakMap()
export function track(target,type,key){
    //activeEffect //当前正在运行的effect
    console.log(activeEffect)
    if(activeEffect === undefined){ //
        return
    }
    //然后收集对应对象中,对应的key
    let depsMap = targetMap.get(target)
    if(!depsMap){
        targetMap.set(target,(depsMap = new Map()))
    }

    let dep = depsMap.get(key) //获取属性,所对应收集的effect数组

    if(!dep){
        depsMap.set(key,(dep = new Set()))
    }



    if(!dep.has(activeEffect)){
        dep.add(activeEffect)
    }
    console.log(targetMap)
}

2.trigger的触发更新

let state = reactive({name:'ly'},age:12,arr:[1,23])





effect(() => {

   app.innerHTML = state.name;

})






setTimeout(() => {
  state.name = 'zs'; // 更改namne属性需要重新执行
}, 1000);

1.在set方法中判断,判断是数组还是对象,判断是新增属性还是添加属性

set(target, key, value, receiver) {




        const oldValue = target[key]; // 获取老的值
      
        //判断是新增还是修改方法
        let hadKey = isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target,key)



        const result = Reflect.set(target, key, value, receiver); // target[key] = value
        if(!hadKey){
            //表示是新增
            trigger(target,'add',key,value)
        }else if(hasChanged(oldValue,value)){
            //表示是修改
            trigger(target,'set',key,value,oldValue)
        }



        return result;
    }
}

2.找到对应的对象下面的属性,所收集的依赖,然后触发更新

export function trigger(target,type,key?,value?,oldValue?){
    //如果这个属性没有收集过effect,那不需要做任何操作

    const depsMap = targetMap.get(target)
    if(!depsMap) return
    const effects = new Set()



    const add = (effectsToAdd) => {
        if(effectsToAdd){
            effectsToAdd.forEach(effect => effects.add(effect))
        }
    }




    if(key !== undefined){
        add(depsMap.get(key)) //修改对象的值
    }
  
    //总结,就是把对应effect收集起来,去重后,在批量执行
    effects.forEach((effect:any) => effect())
}

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYjnMg2I' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片