Vue3:原来你是这样的“异步更新”

本文解析了Vue3中响应式系统的调度执行机制,包括如何通过scheduler控制副作用函数执行时机,以及如何实现批量更新和异步更新,以优化性能。作者引用《Vue.js技术设计与实现》一书内容,推荐对Vue底层感兴趣的读者阅读。
摘要由CSDN通过智能技术生成

写在前面

这是Vue3源码分析的第三篇,与响应式系统中调度执行有关,其中computedwatch等核心功能都离不开它,可见其重要程度。

除了实现可调度性,我们还会借助它来实现vue中一个非常重要的功能,批量更新或者叫异步更新

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

  1. 面试官:Vue3响应式系统都不会写,还敢说精通?

  2. 面试官:你觉得Vue的响应式系统仅仅是一个Proxy?

什么是调度执行?

什么是调度执行?

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

来看个例子

const state = reactive({
  num: 1
})

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

state.num++

console.log('end')


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

1
end
2


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

const state = reactive({
  num: 1
})

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

console.log('end')

state.num++

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

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

如何实现可调度?

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

const state = reactive({
  num: 1
})

effect(() => {
  console.log(state.num)
}, {
  
  
  scheduler (fn) {
    
    setTimeout(() => {
      fn()
    }, 0)
  }
})

state.num++

console.log('end')

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

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

源码实现

虽然可调度性在Vue中非常重要,但实现这个机制却非常简单,我们甚至只要增加两行代码就可以搞定。

第一行代码

const effect = function (fn, options = {}) {
  const effectFn = () => {
   
  }
  
  
  effectFn.options = options
}

第二行代码

function trigger(target, key) {


  effectsToRun.forEach((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++
}

对于页面渲染来说1到101中间的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(() => {
    
    isFlushing = false
  })
}

effect(() => {
  console.log('num', state.num)
}, {
  scheduler (fn) {
    
    jobQueue.add(fn)
    
    flushJob()
  }
})

let count = 100

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


结尾

下一篇请让我们探寻如何通过调度执行实现computedwatch

最近在阅读霍春阳大佬的 《Vue.js技术设计与实现》,本文的内容主要来源于这本书,强烈推荐对Vue底层实现感兴趣的同学阅读。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Web面试那些事儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值