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
}
相关推荐
VX:Fegn08952 小时前
计算机毕业设计|基于springboot + vue教务管理系统(源码+数据库+文档)
vue.js·spring boot·课程设计
梅梅绵绵冰2 小时前
springboot初步1
java·前端·spring boot
星辰_mya2 小时前
nginx之待续-没写完
前端
Irene19912 小时前
Vue3中 <slot >不支持 ref 属性的替代方案
vue.js·ref
GISer_Jing2 小时前
大语言模型Agent入门指南
前端·数据库·人工智能
多仔ヾ2 小时前
Vue.js 前端开发实战之 10-网络请求和 UI 组件库
vue.js
运筹vivo@2 小时前
BUUCTF: [极客大挑战 2019]Upload
前端·web安全·php·ctf
qq_12498707533 小时前
基于Spring Boot的长春美食推荐管理系统的设计与实现(源码+论文+部署+安装)
java·前端·spring boot·后端·毕业设计·美食·计算机毕业设计
运筹vivo@3 小时前
攻防世界: easyupload
前端·web安全·php·ctf