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

相关推荐
苦瓜小生32 分钟前
【前端】【力扣与手撕】十天带你刷完前端算法与手撕,全是最简单好记的最优解法!day4
前端·数据结构·算法·leetcode·面试
mayaairi41 分钟前
Vue2 实战进阶:表单数据收集、指令系统与自定义指令全解析
前端·javascript·vue.js
AIsoft_86881 小时前
最好用的会议纪要APP怎么选?功能横评
面试·职场和发展·iphone
计算机毕设定制辅导-无忧学长1 小时前
《基于Spring Boot传承之光非遗陶瓷烧造产品交易平台的设计与实现》
java·vue.js·spring boot·后端·毕业设计
计算机学姐2 小时前
基于SpringBoot的旅游系统的设计与实现
java·vue.js·spring boot·后端·spring·java-ee·旅游
武哥聊编程2 小时前
【AI实战项目】基于SpringAI+Springboot+Vue的AI智能简历优化助手
vue.js·人工智能·spring boot
雪芽蓝域zzs3 小时前
Vue3 defineProps` / `defineEmits` 是编译器宏,不需要手动 import 导入
前端·javascript·vue.js
小白羊丨3 小时前
问数 Agent 整体架构、意图识别、历史上下文与 NL2SQL
java·面试·架构
志尊宝4 小时前
Vue3 零基础每日笔记(004):模板语法初识——插值表达式、v-bind、v-on 一次学会
javascript·vue.js·笔记
AIGC小尼5 小时前
穿山甲 + 腾讯短剧短视频聚合广告平台|Android+SpringBoot+Vue+Docker 完整部署指南
android·vue.js·spring boot·聚合广告·广告平台·穿山甲广告·腾讯广告