侧边栏壁纸
博主头像
MicroMatrix博主等级

曲则全,枉则直,洼则盈,敝则新,少则得,多则惑。是以圣人抱一为天下式。不自见,故明;不自是,故彰;不自伐,故有功;不自矜,故长。夫唯不争,故天下莫能与之争。古之所谓“曲则全”者,岂虚言哉!诚全而归之。

  • 累计撰写 80 篇文章
  • 累计创建 21 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

vue3源码学习-6-调度器

蜗牛
2022-06-12 / 0 评论 / 0 点赞 / 7 阅读 / 5273 字 / 正在检测是否收录...

前言

经过第5章对于分支切换的操作之后,vue的effect源码就具有了收集需要的依赖,对于改变不必要的数据,不会触发依赖的更新。那么今天就要实现vue3的调度器代码,之前effect只能同步运行代码,无法对于异步操作进行数据更新。
官方的写法是什么样子呢?

let flag = false;
    let runner = effect(() => {
      document.getElementById("app").innerHTML = "年龄:"+state.age 
    },{
      schedule(){
      if(!flag){
        flag = true;
        setTimeout(() => {
          runner();
          flag = false;
        },1000)
      }
    }}
    )
    // runner.effect.stop();
    state.age = 1000;
    state.age = 2000;
    state.age = 3000;
    state.age = 4000;
    state.age = 5000;

上面的代码最后只会渲染stage.age=5000这个样子。并且还具有了可以手动停止依赖的触发的操作stop()

编写stop()函数

首先,先完成能手动停止依赖的和触发。那么我就要有一个stop函数来完成关闭操作,之前通过run里面的函数渲染,使得属性触发了get操作。通过get函数里面编写的track函数,完成了依赖收集。然后通过trigger里面的依赖记录重新触发函数。在第5篇文章中,完成了依赖每次先清空再收集的函数,那么stop不就可以直接通过调用清空依赖,来达到停止触发的操作。同时把active设置为false,来关闭函数的更新。所以stop函数如下编写了。

class ReactiveEffect{
    ......
    constructor(public fn){
        ......
    }
    run(){
        ......
    }
    stop(){
        this.active =false;
        clearupEffect(this)
    }
}

同样的effect函数也要做相应的修改,抛出当前的runner对象,然后通过这个对象上的stop来操作停止命令。

export function effect(fn, options: any = {}) {
  // 这里的fn可以根据状态的变化,重新执行,effect可以嵌套着写
  const _effect = new ReactiveEffect(fn) //创建响应式的effect
  _effect.run() //默认先执行一次

  const runner = _effect.run.bind(_effect)// 绑定this执行
  runner.effect = _effect // 将_effect挂载到runner上 
  return runner

这样就可以在拿到effect到返回函数之后,通过runner.effect.stop()来手动停止执行。

schedule调度函数实现

当用户在effect中的schedule中写了一个自己的一个操作,那么就能通过用户是否写了schedule函数来判断是否要执行用户的schedule,还是执行run()。
了解了大致思路,我们先修改effect函数,增加一个对象,通过对象来传递用户编写的函数

export function effect(fn, options: any = {}) {
  // 这里的fn可以根据状态的变化,重新执行,effect可以嵌套着写
  const _effect = new ReactiveEffect(fn, options.schedule) //创建响应式的effect
  _effect.run() //默认先执行一次

  const runner = _effect.run.bind(_effect) // 绑定this执行
  runner.effect = _effect // 将_effect挂载到runner上 
  return runner
}

同时ReactiveEffect类上在构造函数上加入schedule参数。

class ReactiveEffect{
    ......
    constructor(public fn, public schedule){
        ......
    }
}

下一步就是修改trigger函数,这样变量的属性改变的时候就知道是不是执行schedule函数了

export function trigger(target, type, key, value, oldValue) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return //触发的值不在模版中
  let effects = depsMap.get(key)
  // 此处做逻辑修改,因为set在删除之后,再做添加,那么会造成死循环,有些方法会对数据拷贝之后再做修改
  // 可以避免这个问题
  if (effects) {
    effects = new Set(effects)
    effects.forEach((effect) => {
      if (activeEffect !== effect) {
        if (effect.schedule) {
          effect.schedule() // 用户传入schedule的时候,就调用回调
        } else {
          effect.run() // 否则就刷新
        }
      }
      // 如果这里直接就写effect.run(),那么会遇到这种情况,在模版中赋值,那么也会触发这个,
      // 然后又通过了依赖收集的时候,运行它的第一次run()。就会导致循环调用,爆栈,
      //所以这里需要加一个判断是否是当前的effect,如果是的话,就忽略这一次的赋值触发的run();
      //注意目前的代码是不支持异步的
    })
  }
}

到此为止,就能在测试html上调用自己写的schedule函数了,如开篇中的例子

git:[@github/MicroMatrixOrg/vue3-plan/tree/effect_schedule]
0

评论区