Análisis del principio de reactividad de Vue
/ 10 min read
En los últimos años he leído muchos artículos sobre los principios de Vue, y con la ayuda de estos artículos, he intentado varias veces entender el código fuente de Vue por mí mismo. Finalmente, creo que es hora de compartir mi conocimiento y espero poder familiarizar a todos con Vue desde una perspectiva diferente a la de otros artículos.
Naturalmente, este tema se dividirá en varias partes para explicar el código fuente de Vue. Empezaremos con el principio más clásico de Vue: la reactividad.
Antes de entrar en los detalles del principio, creo que es importante aclarar algunos conceptos:
Dep
Dep significa “dependency” (dependencia en inglés), que es un término utilizado en el campo de la informática.
Es similar a cuando escribimos programas en node.js y usamos las dependencias del repositorio npm. En Vue, una dependencia se refiere específicamente a los datos que han sido procesados de manera reactiva. Más adelante mencionaremos una de las funciones clave en el procesamiento reactivo, que es defineReactive
, que se menciona en muchos artículos sobre los principios de Vue.
Cuando se vincula un objeto Dep a un dato reactivo, ese dato se convierte en una dependencia. Más adelante, cuando hablemos de Watcher, veremos que un dato reactivo puede ser dependencia de tres tipos de funciones: watch, computed y la función de renderizado de la plantilla.
subs
El objeto Dep tiene una propiedad llamada subs, que es un array. Es fácil adivinar que subs significa “subscribers” (suscriptores en inglés). Los suscriptores pueden ser funciones watch, funciones computed o funciones de actualización de la vista.
Watcher
El Watcher es el “suscriptor” mencionado en Dep (no confundir con el Observer que mencionaremos más adelante).
La función del Watcher es responder rápidamente a las actualizaciones de Dep, similar a cómo funcionan las notificaciones de suscripción en algunas aplicaciones. Tú (el Watcher) te suscribes a cierta información (Dep) y se te notifica cuando esa información se actualiza para que puedas leerla.
deps
Al igual que el objeto Dep tiene la propiedad subs, el objeto Watcher tiene la propiedad deps. Esto crea una relación de muchos a muchos entre Watcher y Dep. La razón por la que se registran mutuamente es para poder actualizar los objetos relacionados cuando uno de ellos se elimina.
Cómo se crea un Watcher
En el código fuente de Vue, se puede ver claramente cómo se crean los Watchers que hemos mencionado varias veces:
- En
mountComponent
, se creavm._watcher = new Watcher(vm, updateComponent, noop);
- En
initComputed
, se creawatchers[key] = new Watcher(vm, getter || noop, noop, computedWatcherOptions)
- En
$watcher
, se creavar watcher = new Watcher(vm, expOrFn, cb, options);
Observer
El Observer es el “observador” que se encarga de observar (o procesar) de forma recursiva los objetos (o arrays) reactivos. Si observas las instancias que se imprimen, notarás que los objetos reactivos tienen una propiedad __ob__
, que es una prueba de que han sido observados. El Observer no es tan importante como el Dep y el Watcher, así que solo necesitamos tener una idea general de su funcionamiento.
walk
Observer.prototype.walk
es el método principal que se utiliza para procesar de forma recursiva los objetos cuando se inicializa el Observer. Además de este método, también existe Observer.prototype.observeArray
, que se utiliza para procesar los arrays.
Flujo principal
Teniendo en cuenta la relación entre los conceptos mencionados anteriormente, ¿cómo podemos lograr la actualización reactiva de los datos?
Nuestro objetivo es actualizar automáticamente la vista y mostrar los datos más recientes cuando los datos se actualicen.
Aquí es donde entra en juego la relación entre Dep y Watcher. Los datos son las Dep y el Watcher es el que desencadena la función de renderizado de la página (que es el Watcher más importante).
Pero surge una nueva pregunta: ¿cómo sabe Dep qué Watcher depende de él?
Vue utiliza un método muy interesante para resolver esto:
- Antes de ejecutar la función de devolución de llamada de Watcher, se guarda la información de qué Watcher es actualmente (a través de Dep.target).
- Si se utiliza datos reactivos en la función de devolución de llamada, se llamará a la función getter de los datos reactivos.
- En la función getter de los datos reactivos se puede guardar la información del Watcher actual y establecer la relación entre Dep y Watcher.
- Después, cuando los datos reactivos se actualicen, se llamará a la función setter de los datos reactivos.
- Basado en la relación establecida anteriormente, se puede activar la función de devolución de llamada correspondiente del Watcher en la función setter.
Código
La lógica anterior se encuentra en la función defineReactive
. Esta función tiene varias entradas, pero aquí se explicará primero la función observe
, que es más importante.
En la función observe
, se crea un nuevo objeto Observer y se utiliza Observer.prototype.walk
para realizar el procesamiento reactivo de los valores del objeto uno por uno, utilizando la función defineReactive
.
Debido a que la función defineReactive
es muy importante y no es muy larga, se muestra directamente aquí para mayor comodidad.
First, each property of the reactive object is a “dependency”, so the first step is to create a Dep for each value using the closure capability. (In Vue 3, closures are no longer needed.)
Next, let’s look at the three core parameters:
- obj: the object where the value that needs to be reactive is located
- key: the key of the value
- val: the current value
This value may have its own getter and setter defined, so when processing the reactivity of Vue, we first handle the original getter and setter.
Getter
In the core process mentioned above, it is mentioned that the Dep and Watcher relationship is established in the getter function, specifically relying on dep.depend()
.
Here are a few methods that Dep and Watcher call each other:
Through these functions, you can see the complex relationship between Dep and Watcher… But in essence, it is just adding each other to a many-to-many list, as mentioned above.
Puedes encontrar todos los Watchers que se suscriben al mismo Dep en subs
de Dep, y también puedes encontrar todos los Dep a los que se suscribe el Watcher en deps
de Watcher.
Pero hay una pregunta oculta, ¿cómo se obtiene Dep.target
? Lo dejaremos por ahora y lo responderemos más adelante.
setter
Continuemos viendo la función setter, donde se encuentra la clave dep.notify()
.
No es difícil de entender, simplemente Dep notifica a todos los suscriptores en su lista (subs
) para que se actualicen. Los suscriptores son Watchers, y lo que se llama es Watcher.prototype.update
.
Veamos qué hace update
en el Watcher:
Aquí veo dos puntos que vale la pena mencionar, así que voy a hacer una pausa 😂
- Punto 1: Si no se actualiza de forma sincrónica, se encola en
queueWatcher
. Hablaré más adelante sobre la actualización asíncrona, pero esto también facilita la comprensión aquí. En resumen, solo necesitas saber quequeueWatcher
ejecutarárun
después de una serie de operaciones. - Punto 2: La función
cb
del Watcher puede manejar watch, computed y funciones de actualización del componente. Es especialmente importante la función de actualización del componente, ya que es aquí donde se actualiza la página de Vue. Así que también vale la pena mencionarlo, pero para facilitar la comprensión, solo necesitas saber que la actualización se desencadena aquí y hablaré más adelante sobre los métodos de actualización. - Punto 3: Puedes ver que cuando está en modo
lazy
, no se ejecutan los pasos siguientes, solo se marca que los datos se han actualizado y se calculará un nuevo valor la próxima vez que se acceda a ellos.
El punto clave de este código es que se ha establecido Dep.target
en el método get
actual. (La ruta específica es run -> get -> pushTarget)
Porque solo cuando Dep.target
existe, Dep.prototype.depend
se activará realmente cuando se llame a la función de devolución de llamada cb
(por ejemplo, la función de renderización de la página es un ejemplo típico de Watcher cb
). A continuación, la lógica vuelve a utilizar el valor de los datos reactivos, ¡todo está conectado! ¡Formando un bucle cerrado (risas)! Esta es la respuesta al problema pendiente de depend()
mencionado anteriormente.
Resumen
Dep
está asociado con los datos y representa que los datos pueden ser dependencias.Watcher
tiene tres tipos: watch, computed y función de renderización. Estas funciones pueden ser suscriptores de las dependencias.Observer
es una especie de punto de entrada para manejarDep
, procesando datos reactivos de forma recursiva.- La función de devolución de llamada de
Watcher
estableceDep.target
antes de utilizar los datos reactivos. - Los datos reactivos en la función
getter
conocen al llamador a través deDep.target
y establecen una relación entre suscriptores y dependencias. - Los datos reactivos en la función
setter
notifican a todos los suscriptores ensubs
que los datos se han actualizado. - Cuando el suscriptor es una función de actualización de la vista (
updateComponent
->_update
), el usuario puede ver la actualización de la página cuando los datos reactivos se actualizan, logrando así el efecto de actualización reactivo.
Aunque en general este algoritmo no es difícil de entender, en realidad hay muchos otros mecanismos que trabajan junto con este algoritmo para formar un Vue completo. Por ejemplo, las colas de actualización y la implementación de la función de actualización del componente también son dignas de estudio.
Además, hay más detalles en el código que pueden ser explorados por aquellos interesados.
PD. Debido a mis habilidades de expresión que no son muy buenas y la maldición del conocimiento, no estoy seguro de si este texto realmente explica claramente el principio de reactividad de Vue. Si hay algo que no se entienda, por favor, háganlo saber en la sección de comentarios. Gracias a todos 💡
Único enlace de referencia: Vue.js