Vue3 的“批量渲染”机制

两种机制分别指:

  1. 在 1次 回调里触发 多次 computed/effect,其只会执行 1次
  2. 在 1次 回调里 多次 更新与视图相关的 ref视图更新 也只执行 1次

举两个例子:

js 复制代码
// effectA
effect(() => {
  console.log('A:', a.value)
  if (a.value > 5) {
    // 2、再触发第2次 effectB
    b.value = a.value * 2
  }
})

// effectB
effect(() => {
  console.log('B:', b.value)
})

// 1、更新dep, 触发1次 effectA 和 1次 effectB
a.value = 6
// 实际 effectB 只会执行 1 次,而不是 2 次
js 复制代码
btn.onclick = () => {
  count.value++
  count.value++
  count.value++
  count.value++
}

return () => {
  console.log('render call')
  return h('div', `count: ${count.value}`)
}
// 点击按钮,实际 'render call' 只会被打印1次

如何实现的?简写源码 来说明: PS: 不熟悉响应式基础的,请移步往期文章

effect 的批处理

修改响应式变量的值后,会触发 setter 里的函数 trigger,之后会沿着链表通知所有 effect

js 复制代码
export function traggerRef(dep: RefImpl) {
  ......
    propagate(dep.subs)
  ......
}

/** 传播更新 */
export function propagate(subs: Link) {
  // 链表节点
  let link = subs
  // 收集 effect
  const queuedEffect = []
  while (link) {
    const sub = link.sub
    // 标记 dirty,防止重复触发 effect
    if (!sub.tracking && !sub.dirty) {
      sub.dirty = true
      // ......
      queuedEffect.push(sub)
      // ......
    }
    // 遍历链表
    link = link.nextSub
  }

  // 通知 effect 执行
  queuedEffect.forEach(effect => effect.notify())
}

可以看到 sub 里有一个 dirty 属性,如果同一次回调函数中,多次触发 sub,它只会被放入待执行列表 1 次,也就是不会多次执行。

注意,dirty 标志位会等 Effect 真正执行完成后才重置。

异步渲染 render

mount组件的流程是:使用VNode创建组件实例instance -> 挂载到DOM -> 更新,组件实际上就是创建了一个 Effect 来订阅更新:

js 复制代码
const mountComponent = (vnode, container, anchor) => {
    /**
     * 1、创建组件实例
     * 2、初始化状态
     * 3、挂载到DOM
     */
    // 1 实例化
    const instance = createComponentInstance(vnode)
    // 2 初始化
    setupComponent(instance)

    const componentUpdateFn = () => {
      // 首次挂载
      if (!instance.isMounted) {
        // 得到 Virtual DOM
        const subTree = instance.render()
        // 3 挂载
        patch(null, subTree, container, anchor)
        // 保存当前 V-DOM
        instance.subTree = subTree
        // 修改标志位
        instance.isMounted = true
      } else {
        // 更新
        const preSubTree = instance.subTree
        // 获取新的 V-DOM
        const subTree = instance.render()
        // 对比新旧 VNode,更新
        patch(preSubTree, subTree, container, anchor)
        instance.subTree = subTree
      }
    }

    const effect = new ReactiveEffect(componentUpdateFn)
    const update = effect.run.bind(effect)

    instance.update = update

    effect.scheduler = () => {
      queueJob(update)
    }

    effect.run()
  }

但假如有如下例子,假如点击 1次 按钮,将打印 4次 effect execute 和 1次 render call

js 复制代码
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Title</title>
  </head>
  <body>
    <div id="app"></div>
    <button id="btn">+++</button>
    <script type="module">
      import { createApp, h, ref, effect, computed } from 'vue'

      const rootComp = {
        setup() {
          const count = ref(0)

          btn.onclick = () => {
            count.value++
            count.value++
            count.value++
            count.value++
          }

          effect(() => {
            console.log('effect execute', count.value)
          })

          return () => {
            console.log('render call')
            return h('div', `count1: ${count.value}`)
          }
        },
      }

      createApp(rootComp).mount('#app')
    </script>
  </body>
</html>

按理说 render call 也应该打印 4次,Why? 因为代码里利用 Effect.scheduler 做了 异步更新,即重写了scheduler:

js 复制代码
const componentUpdateFn = () => {
  //......
  const update = effect.run.bind(effect)

  instance.update = update

  effect.scheduler = () => {
    queueJob(update)
  }
  //......
}

function queueJob(job) {
  Promise.resolve().then(() => {
    job()
  })
}

此时,每次 ref 更新后,不立即重置 dirty,而是等所有同步任务执行完后,再执行渲染,BINGO

相关推荐
计算机学姐3 小时前
基于SpringBoo+Vue的医院预约挂号管理系统【个性化推荐算法+可视化统计】
java·vue.js·spring boot·mysql·intellij-idea·mybatis·推荐算法
计算机学姐3 小时前
基于微信小程序的奶茶店点餐平台【2026最新】
java·vue.js·spring boot·mysql·微信小程序·小程序·mybatis
哎哟你干嘛3 小时前
🚀🚀🚀Vue3相对Vue2的全面改进与优化
面试
东方石匠3 小时前
Javascript常见面试题
前端·javascript·面试
性野喜悲3 小时前
<script setup lang=“ts“>+uniapp实现轮播(swiper)效果
前端·javascript·vue.js·小程序·uni-app
change_fate4 小时前
vue3 懒加载第三方组件
javascript·vue.js·ecmascript
前端付豪5 小时前
Vue3 响应式来!
前端·javascript·vue.js
炫饭第一名5 小时前
🌍🌍🌍字节一面场景题:异步任务调度器
前端·javascript·面试
艾莉丝努力练剑5 小时前
【C++:继承和多态】多态加餐:面试常考——多态的常见问题11问
开发语言·c++·人工智能·面试·继承·c++进阶