Vue3批量异步更新是如何实现

一、什么是调度执行

多次修改数据(例如自身num10次),只进行一次页面渲染(页面只会渲染最后一次num10

指的是响应式数据发生变化出发副作用函数重新执行时,我们有能力去决定副作用函数的执行时机次数方式

来看个例子

const state = reactive({
   
  num: 1
})

effect(() => {
   
  console.log('num', state.num)
})

state.num++

console.log('end')

img

如果我们想要它按照这个顺序输出呢?

1
end
2

你可能会说,我调换一下代码顺序就好了哇!!!

const state = reactive({
   
  num: 1
})

effect(() => {
   
  console.log('num', state.num)
})

console.log('end')

state.num++

img

淫才啊!😄 瞬间就解决了问题。不过看起来这不是我们想要最终答案。

我们想要通过实现可调度性来解决这个问题。

二、如何实现可调度?

我们从结果出发来思考如何实现可调度的特性。

const state = reactive({
   
  num: 1
})

effect(() => {
   
  console.log(state.num)
}, {
   
  // 注意这里,假如num发生变化的时候执行的是scheduler函数
  // 那么end将会被先执行,因为我们用setTimeout包裹了一层fn
  scheduler (fn) {
   
    // 异步执行
    setTimeout(() => {
   
      fn()
    }, 0)
  }
})

state.num++

console.log('end')

看到这里也许你已经明白了,我们将通过**scheduler**来自主控制副作用函数的执行时机。

在这之前,执行state.num++之后,console.log(state.num)将会被马上执行,而添加scheduler后,num发生变化后将执行scheduler中的逻辑。

虽然可调度性在Vue中非常重要,但实现这个机制却非常简单。

// 增加options参数
const effect = function (fn, options = {
    }) {
   
  const effectFn = () => {
   
   // ....
  }
  // ...
  // 将options参数挂在effectFn上,便于effectFn执行时可以读取到scheduler
  effectFn.options = options
}
function trigger(target, key) {
   
// ...

  effectsToRun.forEach((effectFn) => {
   
    // 当指定了scheduler时,将执行scheduler而不是注册的副作用函数effectFn
    if (effectFn.options.scheduler) {
   
      effectFn.options.scheduler(effectFn)
    } else {
   
      effectFn()
    }
  })
}

三、批量更新 & 异步更新

来看段诡异的代码,请问num会被执行多少次?100还是101

const state = reactive({
   
  num: 1
})

effect(() => {
   
  console.log('num', state.num)
})

let count = 100

while (count--) {
   
  state.num++
}

img

对于页面渲染来说1101中间的2~100仅仅只是过程,并不是最终的结果,处于性能考虑Vue只会渲染最后一次的101

四、Vue原理

利用可调度性,再加点事件循环的知识,我们就可以做到这件事。

  1. num的每次变化都会导致scheduler的执行,并将注册好的副作用函数存入jobQueue队列,因为Set本身的去重性质,最终只会存在一个fn
  2. 利用Promise微任务的特性,当num被更改100次之后同步代码全部执行结束后,then回调将会被执行,此时num已经是101,而jobQueue中也只有一个fn,所以最终只会打印一次101
const state = reactive({
   
  num: 1
})

const jobQueue = new Set()
const p = Promise.resolve()
let isFlushing = false

const flushJob = () => {
   
  if (isFlushing) {
   
    return
  }

  isFlushing = true
  // 微任务
  p.then(() => {
   
    jobQueue.forEach((job) => job())
  }).finally(() => {
   
    // 结束后充值设置为false
    isFlushing = false
  })
}

effect(() => {
   
  console.log('num', state.num)
}, {
   
  scheduler (fn) {
   
    // 每次数据发生变化都往队列中添加副作用函数
    jobQueue.add(fn)
    // 并尝试刷新job,但是一个微任务只会在事件循环中执行一次,所以哪怕num变化了100次,最后也只会执行一次副作用函数
    flushJob()
  }
})

let count = 100

while (count--) {
   
  state.num++
}

img

五、最后

本人每篇文章都是一字一句码出来,希望对大家有所帮助,多提提意见。顺手来个三连击,点赞👍收藏💖关注✨,一起加油☕

相关推荐

  1. Vue异步更新机制如何实现

    2024-02-02 12:58:01       63 阅读
  2. vue如何实现异步组件

    2024-02-02 12:58:01       36 阅读
  3. vue异步更新 $nextTick

    2024-02-02 12:58:01       51 阅读
  4. Vue 异步更新和 $nextTick】

    2024-02-02 12:58:01       38 阅读
  5. 什么AJAX?如何使用AJAX实现异步数据传输?

    2024-02-02 12:58:01       43 阅读
  6. vue3前端调用后端接口实现批量删除

    2024-02-02 12:58:01       38 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-02-02 12:58:01       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-02-02 12:58:01       101 阅读
  3. 在Django里面运行非项目文件

    2024-02-02 12:58:01       82 阅读
  4. Python语言-面向对象

    2024-02-02 12:58:01       91 阅读

热门阅读

  1. HTML优化SEO的实用技巧

    2024-02-02 12:58:01       51 阅读
  2. undefined reference to symbol ‘pow@@GLIBC_2.0

    2024-02-02 12:58:01       57 阅读
  3. 持续积累分享金融知识

    2024-02-02 12:58:01       45 阅读
  4. SparkSql Join Types详解

    2024-02-02 12:58:01       50 阅读
  5. C++入门学习(二十三)选择结构-switch语句

    2024-02-02 12:58:01       58 阅读
  6. React状态管理Zustand简单介绍和使用

    2024-02-02 12:58:01       51 阅读
  7. 如何在redis中存储ndarray

    2024-02-02 12:58:01       49 阅读
  8. 【C语言】深入理解NULL指针

    2024-02-02 12:58:01       48 阅读
  9. github开源代码流程-初始化配置 quick start(2)

    2024-02-02 12:58:01       51 阅读