36-mini-vue nextTick

实现 nextTick 功能

  1. 在实现 nextTick 功能前,我们先实现 render 函数的异步渲染,为什么 render 函数的视图渲染是异步渲染呢?
  2. 我们先初始化文件
js 复制代码
import { h, ref } from '../../lib/guide-mini-vue.esm.js'

export const App = {
  name: 'App',
  setup() {
    const count = ref(1)

    function onClick() {
      for (let i = 0; i < 100; i++) {
        console.log('update');
        count.value = i
      }
    }
    return {
      count,
      onClick
    }
  },
  render() {
    const button = h("button", { onClick: this.onClick }, "update")
    const p = h('p', {}, "count" + this.count)
    return h("div", {}, [
      button,
      p
    ])
  }
}
  1. 我们看到点击button,循环赋值100次,如果是同步渲染,就要依次渲染100次,这是相当消耗性能的,做成异步渲染,再使用变量控制执行时机,能够做到同步执行完以后再一次性进行异步渲染,简单方便,我们实现一下
js 复制代码
// 设计程序
// 变量的响应式赋值触发视图更新,每次赋值触发一次更新job,将视图更新放入队列中,正好 effect 有一个 scheduler 的方法,之前我们封装过,配置这个方法后,变量响应式更新就开始走这里 scheduler 配置的方法,在这里可以将 effect 的返回的视图更新,放入异步队列中

// renderer.ts
function setupRenderEffect(instance, vnode, container, anchor) {
  instance.update = effect(() => {
    let { proxy } = instance
    if (!instance.isMounted) {
      const subTree = instance.subTree = instance.render.call(proxy)
      patch(null, subTree, container, instance, anchor)
      // 在所有 element 都已经挂载完毕后,才能够拿到 虚拟节点
      vnode.el = subTree.el
      instance.isMounted = true
    } else {
      // 需要一个更新完成之后的 vnode
      const { next, vnode } = instance // 这里的 vnode 是我们更新之前的虚拟节点,next是我们下次要更新的虚拟节点
      if (next) {
        next.el = vnode.el
        updateComponentPreRender(instance, next)
      }
      const { proxy } = instance
      const subTree = instance.render.call(proxy)
      const prevSubTree = instance.subTree
      instance.subTree = subTree
      patch(prevSubTree, subTree, container, instance, anchor)
    }
  },{
    scheduler() { // ✅
      queueJobs(instance.update) 
    }
  })
}
// scheduler.ts ✅
let queue: any[] = []
export function queueJobs(job) {
  if (!queue.includes(job)) {
    queue.push(job)
  }
  queryFlush()
}
function queryFlush() {
  Promise.resolve().then(() => {
    let job
    while (job = queue.shift()) {
      job && job()
    }
  })
}
  1. 上面我们把更新的逻辑转为异步队列,不过点击以后这个异步队列还是会执行 100 次,我们需要一个锁来控制该队列仅执行一次
js 复制代码
let queue: any[] = []
let isFlushPending = false // ✅
export function queueJobs(job) {
  if (!queue.includes(job)) {
    queue.push(job)
  }
  queryFlush()
}
function queryFlush() {
  if(isFlushPending) return // ✅
  isFlushPending = true // ✅
  Promise.resolve().then(() => {
    isFlushPending = false // ✅
    let job
    while (job = queue.shift()) {
      job && job()
    }
  })
}
// 这里的 queryFlush 是函数调用,属于同步任务,第一次执行会一直往下走,走到 Promise.resolve 微任务,这个微任务放到执行栈中,同时用锁将后面逻辑锁住,等到同样的 queryFlush 函数调用时,不走微任务,最终同步任务执行完毕,仅执行第一次调用时挂在执行栈的微任务 
  1. 我们已经实现了异步渲染,我们想要在循环后面直接获取dom的内容,就需要实现 nextTick 功能
js 复制代码
import { getCurrentInstance, h, ref } from '../../lib/guide-mini-vue.esm.js'

export const App = {
  name: 'App',
  setup() {
    const count = ref(1)
    const instance = getCurrentInstance()
    function onClick() {
      for (let i = 0; i < 100; i++) {
        count.value = i
      }
      // 这里数据已经变为99,但我们拿到页面上的数据还是 1
      console.log(instance);
      // 所以我们要实现 nextTick
      console.log(instance);
      nextTick(()=> {
        console.log(instance);
      }) 
      await nextTick()
      console.log(instance);
    }
    return {
      count,
      onClick
    }
  },
  render() {
    const button = h("button", { onClick: this.onClick }, "update")
    const p = h('p', {}, "count" + this.count)
    return h("div", {}, [
      button,
      p
    ])
  }
}
  1. 我们对 nextTick 进行实现
js 复制代码
let queue: any[] = []
let isFlushPending = false

export function nextTick(fn) { // ✅
  return fn ? Promise.resolve(fn) : Promise.resolve()
}
export function queueJobs(job) {
  if (!queue.includes(job)) {
    queue.push(job)
  }
  queryFlush()
}
function queryFlush() {
  if (isFlushPending) return
  isFlushPending = true
  nextTick(flushJobs) // ✅
}
function flushJobs() { // ✅ 将 Promise.resolve 抽离出来实现 nextTick 
  isFlushPending = false
  let job
  while (job = queue.shift()) {
    job && job()
  }
}
  1. 效果实现我们进一步优化
js 复制代码
const p = Promise.resolve()

export function nextTick(fn) { // ✅
  return fn ? p.then(fn) : p
}
相关推荐
zzjyr几秒前
@umijs/max 中导出的 request 方法,如何实现 GET/POST/PUT/DELETE 这四种核心请求
前端
swipe几秒前
#用这 9 个浏览器 API,我把页面从“卡成 PPT”救回到 90+(每个都有能直接抄的例子)
前端·javascript·面试
zzjyr2 分钟前
基于 @umijs/max 的 request 补充常见错误统一处理、请求取消、重复请求防抖的完整方案
前端
拖拉斯旋风14 分钟前
深入浅出 RAG:从网页爬取到智能问答的完整链路解析
前端
Mintopia27 分钟前
Vite 发展现状与回顾:从“极致开发体验”到生态基础设施
前端
前端双越老师1 小时前
前端面试常见的 10 个场景题
前端·面试·求职
孟祥_成都2 小时前
【全网最通俗!新手到AI全栈开发必读】 AI 是如何进化到大模型的
前端·人工智能·全栈
牛奶2 小时前
AI辅助开发的基础概念
前端·人工智能·ai编程
摸鱼的春哥2 小时前
Agent教程15:认识LangChain,Agent框架的王(上)
前端·javascript·后端
明月_清风3 小时前
自定义右键菜单:在项目里实现“选中文字即刻生成新提示”
前端·javascript