Aprendizaje del código fuente de Vue: cola de actualización asíncrona y principio nextTick

prefacio

En el proceso de uso de Vue, básicamente la mayoría de watcherlas actualizaciones deben procesarse 异步更新. Y nextTickes el núcleo de la actualización asíncrona.

Definición oficial de la misma:

Devolución de llamada retrasada para ejecutar después de que finalice el próximo ciclo de actualización de DOM. Utilice este método inmediatamente después de modificar los datos para obtener el DOM actualizado.

1. Vue actualiza la cola de forma asíncrona

Vue puede hacerlo 数据驱动视图更新, simplemente escribamos un caso para lograrlo:

<template>
  <h1 style="text-align:center" @click="handleCount">{
   
   { value }}</h1>
</template>

<script>
export default {
      
      
  data () {
      
      
    return {
      
      
      value: 0
    }
  },
  methods: {
      
      
    handleCount () {
      
      
      for (let i = 0; i <= 10; i++) {
      
      
        this.value = i
        console.log(this.value)
      }
    }
  }
}
</script>

Vue actualiza dom de forma asíncrona

valueCuando activamos este evento, debe haber algunos cambios en la vista .
Aquí puede pensar en cómo gestiona Vue este proceso de cambio. Por ejemplo, en el caso anterior, valuese repitió 10 veces, entonces, ¿Vue representará la vista dom 10 veces? Obviamente no, después de todo, el costo de rendimiento es demasiado alto. En realidad solo necesitamos valuela última asignación.

De hecho, Vue actualiza la vista de forma asincrónica, es decir, después de que handleCount()se ejecuta el evento, se encuentra que solo necesita actualizarse value, y luego los datos y Dom se actualizan al mismo tiempo para evitar actualizaciones no válidas.

En resumen, la actualización de datos de Vue y la actualización de DOM son asíncronas. Vue agregará cambios de datos a la cola, realizará actualizaciones por lotes en el siguiente ciclo de eventos y luego aplicará los cambios a los elementos DOM reales de forma asíncrona para mantener la vista y la sincronización de datos.

La documentación oficial de Vue también confirma nuestros pensamientos, de la siguiente manera:

异步Vue se ejecuta al actualizar el DOM . Siempre que escuche 数据变化, Vue abrirá una cola y almacenará en búfer todos los cambios de datos que se produzcan en el mismo bucle de eventos. Si el mismo observador se activa varias veces, solo se colocará en la cola una vez. Esta deduplicación durante el almacenamiento en búfer es muy importante para evitar cálculos innecesarios y manipulaciones DOM. Luego, en el siguiente "tick" del bucle de eventos, Vue vacía la cola y realiza el trabajo real (deduplicado).

Para obtener más información, consulte: Documentación oficial de Vue - Cola de actualización asíncrona

Dos, siguiente uso de Tick

Mire el ejemplo, por ejemplo, cuando cambia el contenido del DOM, necesitamos obtener la última altura del elemento.

<template>
  <div>{
   
   { name }}</div>
</template>

<script>
export default {
      
      
  data () {
      
      
    return {
      
      
      name: ''
    }
  },
  methods: {
      
      },
  mounted () {
      
      
    console.log(this.$el.clientHeight)
    this.name = '铁锤妹妹'
    console.log(this.name, 'name')
    console.log(this.$el.clientHeight)
    this.$nextTick(() => {
      
      
      console.log(this.$el.clientHeight)
    })
  }
}
</script>

inserte la descripción de la imagen aquí

Se puede ver en los resultados de la impresión que, aunque los datos del nombre se han actualizado, la altura de los dos primeros elementos es 0, y el valor de Dom actualizado solo se puede obtener en nextTick. ¿Cuál es el motivo específico? Analicemos su principio a continuación.

Este ejemplo también puede referirse al aprendizaje: el monitoreo de vigilancia y $nextTick se usan en combinación para procesar el método de operación una vez que se completa la representación de datos.

3. Análisis de principios

Al ejecutar this.name = '铁锤妹妹', se activará una actualización Watchery el observador se pondrá en cola.

// src/core/observer/watcher.ts
update () {
    
    
    if (this.lazy) {
    
    
        // 如果是计算属性
        this.dirty = true
    } else if (this.sync) {
    
    
        // 如果要同步更新
        this.run()
    } else {
    
    
        // 将 Watcher 对象添加到调度器队列中,以便在适当的时机执行其更新操作。
        queueWatcher(this)
    }
}

La razón para usar la cola es que, por ejemplo, varios cambios de datos, si la vista se actualiza varias veces directamente, el rendimiento se reducirá, por lo que se crea una cola de actualización asíncrona para la actualización de la vista para evitar cálculos innecesarios y operaciones DOM. En la siguiente ronda del bucle de eventos, la cola se actualiza y el trabajo desduplicado (la función de devolución de llamada de nextTick) se ejecuta, el componente se vuelve a representar y la vista se actualiza.

Luego llame a nextTick(), el código fuente de la actualización de distribución receptiva es el siguiente:

// src/core/observer/scheduler.ts

export function queueWatcher(watcher: Watcher) {
    
    
    // ...
    
   // 因为每次派发更新都会引起渲染,所以把所有 watcher 都放到 nextTick 里调用
    nextTick(flushSchedulerQueue)
}

function flushSchedulerQueue () {
    
    
    queue.sort(sortCompareFn)
    for (index = 0; index < queue.length; index++) {
    
    
        watcher = queue[index]
        watcher.run()
        // ...省略细节代码
    }
}

El método de parámetro aquí flushSchedulerQueuese colocará en el bucle de eventos. Después de que se ejecute la tarea del subproceso principal, se ejecutará esta función, y el método correspondiente al observador se ejecutará para la cola del observador 排序, y luego se renderizará para actualizar la vista.遍历run()

Es decir this.name = '铁锤妹妹', cuando, la cola de tareas se entiende simplemente así [flushSchedulerQueue].

La siguiente línea console.log(this.name, 'name')comprueba si los datos del nombre están actualizados.

Luego, la siguiente línea console.log(this.$el.clientHeight), porque la tarea de actualización de la vista flushSchedulerQueueno se ha ejecutado en la cola de tareas, por lo que no se puede obtener la vista actualizada.

Luego, this.$nextTick(fn)al ejecutar, agregue una tarea asíncrona, y la cola de tareas simplemente se entiende así [flushSchedulerQueue, fn].

Luego 同步任务se ejecutan todas, y luego las tareas en la cola de tareas se ejecutan en orden 异步任务. La ejecución de la primera tarea actualizará la vista y, naturalmente, la vista actualizada se obtendrá más tarde.

Cuarto, análisis del código fuente nextTick

1) Juicio ambiental

Juzgue principalmente qué macrotarea o microtarea usar, porque la macrotarea lleva más tiempo que la microtarea , por lo que se usa primero 微任务, y el orden de juicio es el siguiente:
Promise =》 MutationObserver =》 setImmediate =》 setTimeout

// src/core/util/next-tick.ts

export let isUsingMicroTask = false  // 是否启用微任务开关

const callbacks: Array<Function> = [] //回调队列
let pending = false  // 异步控制开关,标记是否正在执行回调函数

// 该方法负责执行队列中的全部回调
function flushCallbacks() {
    
    
  // 重置异步开关
  pending = false
  // 防止nextTick里有nextTick出现的问题
  // 所以执行之前先备份并清空回调队列
  const copies = callbacks.slice(0)
  callbacks.length = 0
   // 执行任务队列
  for (let i = 0; i < copies.length; i++) {
    
    
    copies[i]()
  }
}

// timerFunc就是nextTick传进来的回调等... 细节不展开
let timerFunc
// 判断当前环境是否支持原生 Promise
if (typeof Promise !== 'undefined' && isNative(Promise)) {
    
    
  const p = Promise.resolve()
  timerFunc = () => {
    
    
    p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if (
  !isIE &&
  typeof MutationObserver !== 'undefined' &&
  (isNative(MutationObserver) ||
    // PhantomJS and iOS 7.x
    MutationObserver.toString() === '[object MutationObserverConstructor]')
) {
    
    
  // 当原生 Promise 不可用时,timerFunc 使用原生 MutationObserver
  // MutationObserver不要在意它的功能,其实就是个可以达到微任务效果的备胎
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    
    
    characterData: true
  })
  timerFunc = () => {
    
    
  // 使用 MutationObserver
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
    
    
  // 使用setImmediate,虽然也是宏任务,但是比setTimeout更好
  timerFunc = () => {
    
    
    setImmediate(flushCallbacks)
  }
} else {
    
    
  // 最后的倔强,timerFunc 使用 setTimeout
  timerFunc = () => {
    
    
    setTimeout(flushCallbacks, 0)
  }
}

Luego ingrese el núcleo nextTick.

2)siguienteMarca()

No hay mucho código aquí, la lógica principal es:

  • Coloque la función de devolución de llamada entrante en la cola de devolución de llamada callbacks.
  • La ejecución de la tarea asincrónica guardada timeFuncatravesará callbacksy ejecutará la función de devolución de llamada correspondiente.
export function nextTick(cb?: (...args: any[]) => any, ctx?: object) {
    
    
  let _resolve
  // 把回调函数放入回调队列
  callbacks.push(() => {
    
    
    if (cb) {
    
    
      try {
    
    
        cb.call(ctx)
      } catch (e: any) {
    
    
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
    
    
      _resolve(ctx)
    }
  })
  if (!pending) {
    
    
    // 如果异步开关是开的,就关上,表示正在执行回调函数,然后执行回调函数
    pending = true
    timerFunc()
  }
  // 如果没有提供回调,并且支持 Promise,就返回一个 Promise
  if (!cb && typeof Promise !== 'undefined') {
    
    
    return new Promise(resolve => {
    
    
      _resolve = resolve
    })
  }
}

Puede ver que hay uno devuelto al final Promise, que se puede usar cuando no pasamos parámetros, de la siguiente manera

this.$nextTick().then(()=>{
    
     ... })

5. Suplemento

  • En el ciclo de vida de vue, si created()la operación DOM se realiza en el enlace, también debe colocarse nextTick()en la función de devolución de llamada de .
  • Debido a que en created()la función de enlace, la página está DOMquieta 未渲染y no hay forma de operar el DOM en este momento, por lo que si desea operar el DOM en este momento, debe colocar el código de operación en la nextTick()función de devolución de llamada de

Este es el final de este artículo, espero que sea útil para todos.

Supongo que te gusta

Origin blog.csdn.net/weixin_45811256/article/details/131814963
Recomendado
Clasificación