El mecanismo de actualización asíncrona de Vue.
/ 10 min read
Este artículo se centra en los principios relacionados con las actualizaciones asíncronas en Vue. La comprensión central de este artículo no es tan difícil como los principios de la reactividad, se centra en una palabra clave: cola.
Actualizaciones asíncronas
¿Por qué necesitamos actualizaciones asíncronas?
Piénsalo, según el principio de reactividad de Vue que se analizó en el artículo anterior, cuando se asignan valores a datos reactivos, se ejecuta el setter y se actualiza la vista. Pero como se muestra en el ejemplo anterior, es muy común tener escenarios de asignación continua. En React, puedes escribirlo dentro de un setState
, pero Vue no tiene ese método.
No se puede actualizar la página inmediatamente cada vez que se actualizan los datos. Si cada asignación ejecuta render
y patch
, ¿no se volvería la página extremadamente lenta?
La clave para resolver este problema es almacenar las funciones de actualización que deben ejecutarse en una cola (y al agregarlas a la cola, las funciones duplicadas no se agregarán nuevamente) y luego ejecutar la cola de forma asíncrona.
Variables relacionadas
Antes de analizar en detalle el principio de la cola asíncrona de Vue, echemos un vistazo a estas variables utilizadas para implementar la cola:
Es posible que te preguntes por qué hay dos variables similares, waiting
y flushing
.
Al principio, también me confundía. ¿Está ocurriendo el proceso de flushing? ¿Está esperando el proceso de flushing (waiting)? Aunque hay una ligera diferencia:
- Durante
waiting
, la tarea asíncrona no ha comenzado. - Durante
flushing
, se ha confirmado que se ha iniciado la tarea de la cola.
Hay una pequeña diferencia en el momento de inicio, pero el momento de reinicio (resetSchedulerState
) es el mismo para ambas variables. Parece que no hay necesidad de tener dos variables, así que por ahora lo entenderé como una forma de hacer que el código sea más autoexplicativo.
Volviendo a la cola, este sistema tiene dos colas. Como se mencionó en el artículo anterior, queueWatcher
coloca el watcher que necesita actualizarse en la cola para actualizarlo en la siguiente ocasión. Ahora, analicemos en detalle qué operaciones realiza queueWatcher
.
queueWatcher
La última línea nextTick(flushSchedulerQueue)
es para poner la cola de observadores en la cola de nextTick (aunque no se pasa ningún argumento, flushSchedulerQueue
puede leer la cola
que queueWatcher
maneja).
Esta breve llamada de función conecta dos puntos clave: flushSchedulerQueue
es el núcleo para manejar la cola de observadores, mientras que nextTick
es el núcleo para el renderizado asíncrono de Vue.
flushSchedulerQueue
Según el comentario oficial, queue.sort
ordena la cola para garantizar:
- Los componentes padres se ejecutan antes que los componentes hijos.
- Las funciones de observadores definidas por el usuario se ejecutan antes que los observadores de renderizado.
- Si un componente se destruye antes que su componente padre, se puede omitir este observador (a través de la propiedad activa del observador).
En resumen, flushSchedulerQueue
se utiliza para manejar la cola
que queueWatcher
ha organizado.
Aquí hay una pregunta práctica simple: ¿Se puede acceder al último DOM en la función de devolución de llamada de un observador que observa un valor?
La respuesta se encuentra en el código anterior: durante el bucle de procesamiento de queue
en la función flushSchedulerQueue
, queue
sigue aceptando observadores que queueWatcher
ha insertado. Esto se utiliza principalmente para las funciones de observadores personalizadas o las actualizaciones desencadenadas entre componentes, que se insertan directamente en la cola
en orden de actualización, sin esperar al siguiente bucle.
Por lo tanto, no asuma que al observar un cambio en un valor, puede obtener el último DOM en la función de devolución de llamada, porque se ejecutan en el mismo tick (y la función de renderizado del componente siempre se ejecuta al final debido a la clasificación).
nextTick
Let me explain how Vue creates asynchronous execution:
Déjame explicarte cómo Vue crea la ejecución asíncrona:
Un mecanismo de aplazamiento asíncrono. Antes de la versión 2.4, solíamos usar microtareas (Promise/MutationObserver), pero las microtareas tienen una prioridad demasiado alta y se ejecutan entre eventos secuenciales (por ejemplo, #4521, #6690) o incluso entre la propagación del mismo evento (#6566). Técnicamente, setImmediate
debería ser la elección ideal, pero no está disponible en todas partes; y el único polyfill que encola consistentemente la devolución de llamada después de todos los eventos DOM desencadenados en el mismo bucle es mediante el uso de MessageChannel
.
Según los comentarios oficiales, inicialmente Vue utilizaba microtareas como Promise para actualizar la cola, pero se encontró que el momento de actualización no era el adecuado, se insertaba la actualización en momentos incorrectos. Luego se realizó una modificación.
Aquí, Vue proporciona 4 formas de crear llamadas asíncronas:
setImmediate
MessageChannel
, se puede utilizar en IE 10 o superior, es muy sorprendente, no lo sabrías si no lo buscas, el alcance deMessageChannel
es incluso mayor que el dePromise
.Promise
, según los comentarios, se utiliza en Weex.setTimeout 0
, se utiliza en IE 10 o inferior.
¿En resumen, qué es nextTick
?
Desde el código, se puede ver que es una función de ejecución inmediata que devuelve una función queueNextTick
. Por lo tanto, en realidad estamos utilizando queueNextTick(cb, ctx)
(aunque en realidad no entiendo por qué se envuelven las variables callbacks
, timerFunc
, etc. en un cierre en lugar de colocarlas directamente afuera).
En el caso de nextTick(flushSchedulerQueue)
, flushSchedulerQueue
es la función de devolución de llamada cb
.
- En primer lugar,
cb
se inserta encallbacks
, que es una cola que se ejecuta de forma asíncrona y no tiene nada que ver con la cola de watchers. - Según la condición
!pending
, cuando hay unpending
, se salta directamente la ejecución detimerFunc
hasta que se complete el últimonextTickHandler
.
Por cierto, lo que normalmente usamos como $nextTick
es en realidad lo mismo que la función nextTick
:
Resumen
Dos colas:
- La cola de
queueWatcher
- La cola de
callbacks
denextTick
La cola es más flexible y conveniente para agregar componentes que necesitan modificarse de manera oportuna durante la actualización.
Aunque callbacks
parece ser una cola y también es un array, cuando waiting
es true
, no se agregan elementos a callbacks
. callbacks
debería mantenerse en un estado en el que solo haya una función en espera. (En teoría, debería ser así, pero no sé cómo verificarlo, si hay algún error, por favor corrígeme).
Flujo:
- El propósito de la actualización asincrónica es evitar la ejecución repetida de la función de actualización y hacer que la ejecución sea más fluida.
- Después de que el setter activa el watcher, la función de devolución de llamada no se ejecuta de inmediato, sino que se agrega el watcher a la cola
queue
a través de la funciónqueueWatcher
. - Cuando está inactivo, la función
queueWatcher
ejecutanextTick(flushSchedulerQueue)
para que laqueue
se ejecute en el siguiente tick. - La función
flushSchedulerQueue
ejecuta las funciones de la cola de watchers. nextTick
es similar a$nextTick
que se usa comúnmente, es decir, coloca la ejecución de la cola en el siguiente tick.- Vue tiene 4 métodos para generar el próximo tick: setImmediate, MessageChannel, Promise, setTimeout.