Reactividad bajo demanda en Vue 3

Publicado: 2022-03-11

Además de admirables mejoras de rendimiento, el Vue 3 lanzado recientemente también trajo varias características nuevas. Podría decirse que la introducción más importante es la API de composición . En la primera parte de este artículo, recapitulamos la motivación estándar para una nueva API: mejor organización y reutilización del código. En la segunda parte, nos centraremos en aspectos menos discutidos del uso de la nueva API, como la implementación de características basadas en reactividad que eran inexpresables en el sistema de reactividad de Vue 2.

Nos referiremos a esto como reactividad bajo demanda . Después de presentar las nuevas características relevantes, crearemos una aplicación de hoja de cálculo simple para demostrar la nueva expresividad del sistema de reactividad de Vue. Al final, discutiremos qué uso en el mundo real podría tener esta mejora en la reactividad bajo demanda.

Novedades de Vue 3 y por qué es importante

Vue 3 es una reescritura importante de Vue 2, que presenta una gran cantidad de mejoras al tiempo que conserva la compatibilidad con versiones anteriores con la antigua API casi en su totalidad.

Una de las características nuevas más importantes de Vue 3 es la API de composición . Su introducción provocó mucha controversia cuando se discutió públicamente por primera vez. En caso de que aún no esté familiarizado con la nueva API, primero describiremos la motivación detrás de ella.

La unidad habitual de organización del código es un objeto JavaScript cuyas claves representan varios tipos posibles de una parte de un componente. Por lo tanto, el objeto puede tener una sección para datos reactivos ( data ), otra sección para propiedades calculadas ( computed ), una más para métodos de componentes ( methods ), etc.

Bajo este paradigma, un componente puede tener múltiples funcionalidades no relacionadas o vagamente relacionadas cuyo funcionamiento interno se distribuye entre las secciones de componentes antes mencionadas. Por ejemplo, podríamos tener un componente para cargar archivos que implemente dos funcionalidades esencialmente separadas: administración de archivos y un sistema que controla la animación del estado de carga.

La parte <script> podría contener algo como lo siguiente:

 export default { data () { return { animation_state: 'playing', animation_duration: 10, upload_filenames: [], upload_params: { target_directory: 'media', visibility: 'private', } } }, computed: { long_animation () { return this.animation_duration > 5; }, upload_requested () { return this.upload_filenames.length > 0; }, }, ... }

Hay beneficios en este enfoque tradicional de la organización del código, principalmente en que el desarrollador no tiene que preocuparse por dónde escribir una nueva pieza de código. Si estamos agregando una variable reactiva, la insertamos en la sección de data . Si estamos buscando una variable existente, sabemos que debe estar en la sección de data .

Este enfoque tradicional de dividir la implementación de la funcionalidad en secciones ( data , computed , etc.) no es adecuado en todas las situaciones.

Las siguientes excepciones se citan a menudo:

  1. Tratar con un componente con un gran número de funcionalidades. Si queremos actualizar nuestro código de animación con la capacidad de retrasar el inicio de la animación, por ejemplo, tendremos que desplazarnos/saltar entre todas las secciones relevantes del componente en un editor de código. En el caso de nuestro componente de carga de archivos, el componente en sí es pequeño y la cantidad de funcionalidades que implementa también es pequeña. Por lo tanto, en este caso, saltar entre las secciones no es realmente un problema. Este problema de la fragmentación del código se vuelve relevante cuando tratamos con componentes grandes.
  2. Otra situación en la que falta el enfoque tradicional es la reutilización de código. A menudo necesitamos hacer una combinación específica de datos reactivos, propiedades calculadas, métodos, etc., disponibles en más de un componente.

Vue 2 (y el Vue 3 compatible con versiones anteriores) ofrecen una solución para la mayoría de los problemas de organización y reutilización del código: mixins .

Pros y contras de Mixins en Vue 3

Los mixins permiten extraer las funcionalidades de un componente en una unidad de código separada. Cada funcionalidad se coloca en un mixin separado y cada componente puede usar uno o más mixins. Las piezas definidas en un mixin se pueden usar en un componente como si estuvieran definidas en el propio componente. Los mixins son un poco como clases en lenguajes orientados a objetos en el sentido de que recopilan el código relacionado con una funcionalidad determinada. Al igual que las clases, los mixins se pueden heredar (utilizar) en otras unidades de código.

Sin embargo, razonar con mixins es más difícil ya que, a diferencia de las clases, los mixins no necesitan diseñarse teniendo en cuenta la encapsulación. Se permite que los mixins sean colecciones de piezas de código vagamente unidas sin una interfaz bien definida con el mundo exterior. Usar más de un mixin a la vez en el mismo componente puede resultar en un componente que es difícil de comprender y usar.

La mayoría de los lenguajes orientados a objetos (p. ej., C# y Java) desalientan o incluso no permiten la herencia múltiple a pesar de que el paradigma de la programación orientada a objetos tiene las herramientas para lidiar con tal complejidad. (Algunos lenguajes permiten la herencia múltiple, como C++, pero aún se prefiere la composición a la herencia).

Un problema más práctico que puede ocurrir cuando se usan mixins en Vue es la colisión de nombres, que ocurre cuando se usan dos o más mixins que declaran nombres comunes. Cabe señalar aquí que si la estrategia predeterminada de Vue para lidiar con las colisiones de nombres no es ideal en una situación determinada, el desarrollador puede ajustar la estrategia. Esto tiene el costo de introducir más complejidad.

Otro problema es que los mixins no ofrecen algo parecido a un constructor de clases. Esto es un problema porque a menudo necesitamos que una funcionalidad muy similar, pero no exactamente igual, esté presente en diferentes componentes. Esto se puede eludir en algunos casos simples con el uso de fábricas de mezclas.

Por lo tanto, los mixins no son la solución ideal para la organización y reutilización del código, y cuanto más grande es el proyecto, más graves se vuelven sus problemas. Vue 3 presenta una nueva forma de resolver los mismos problemas relacionados con la organización y reutilización del código.

API de composición: la respuesta de Vue 3 a la organización y reutilización del código

La API de Composición nos permite (pero no nos exige) desacoplar completamente las piezas de un componente. Cada fragmento de código (una variable, una propiedad calculada, un reloj, etc.) se puede definir de forma independiente.

Por ejemplo, en lugar de tener un objeto que contiene una sección data que contiene una clave animation_state con el valor (predeterminado) "playing", ahora podemos escribir (en cualquier parte de nuestro código JavaScript):

 const animation_state = ref('playing');

El efecto es casi el mismo que declarar esta variable en la sección de data de algún componente. La única diferencia esencial es que necesitamos hacer que la ref definida fuera del componente esté disponible en el componente donde pretendemos usarla. Hacemos esto importando su módulo al lugar donde se define el componente y devolvemos la ref de la sección de setup de un componente. Omitiremos este procedimiento por ahora y solo nos centraremos en la nueva API por un momento. La reactividad en Vue 3 no requiere un componente; en realidad es un sistema autónomo.

Podemos usar la variable animation_state en cualquier ámbito al que importemos esta variable. Después de construir una ref , obtenemos y establecemos su valor real usando ref.value , por ejemplo:

 animation_state.value = 'paused'; console.log(animation_state.value);

Necesitamos el sufijo '.value' ya que, de lo contrario, el operador de asignación asignaría el valor (no reactivo) "en pausa" a la variable animation_state . La reactividad en JavaScript (tanto cuando se implementa a través de defineProperty como en Vue 2, como cuando se basa en un Proxy como en Vue 3) requiere un objeto cuyas claves podamos trabajar de forma reactiva.

Tenga en cuenta que este también fue el caso en Vue 2; allí, teníamos un componente como prefijo de cualquier miembro de datos reactivos ( component.data_member ). A menos y hasta que el estándar del lenguaje JavaScript introduzca la capacidad de sobrecargar el operador de asignación, las expresiones reactivas requerirán un objeto y una clave (por ejemplo, animation_state y value como se indicó anteriormente) para aparecer en el lado izquierdo de cualquier operación de asignación en la que deseemos preservar la reactividad.

En las plantillas, podemos omitir .value ya que Vue tiene que preprocesar el código de la plantilla y puede detectar automáticamente las referencias:

 <animation :state='animation_state' />

En teoría, el compilador de Vue también podría preprocesar la parte <script> de un componente de archivo único (SFC) de manera similar, insertando .value donde sea necesario. Sin embargo, el uso de refs diferiría en función de si usamos SFC o no, por lo que tal vez tal característica ni siquiera sea deseable.

A veces, tenemos una entidad (por ejemplo, un objeto Javascript o una matriz) que nunca pretendemos reemplazar con una instancia completamente diferente. En cambio, es posible que solo nos interese modificar sus campos con clave. Hay una forma abreviada en este caso: usar reactive en lugar de ref nos permite prescindir del .value :

 const upload_params = reactive({ target_directory: 'media', visibility: 'private', }); upload_params.visibility = 'public'; // no `.value` needed here // if we did not make `upload_params` constant, the following code would compile but we would lose reactivity after the assignment; it is thus a good idea to make reactive variables ```const``` explicitly: upload_params = { target_directory: 'static', visibility: 'public', };

La reactividad desacoplada con ref y reactive no es una característica completamente nueva de Vue 3. Se introdujo parcialmente en Vue 2.6, donde tales instancias desacopladas de datos reactivos se denominaron "observables". En su mayor parte, uno puede reemplazar Vue.observable con reactive . Una de las diferencias es que acceder y mutar el objeto pasado a Vue.observable directamente es reactivo, mientras que la nueva API devuelve un objeto proxy, por lo que mutar el objeto original no tendrá efectos reactivos.

Comparación: API de opciones frente a API de composición.

Lo que es completamente nuevo en Vue 3 es que ahora también se pueden definir de forma independiente otras piezas reactivas de un componente, además de los datos reactivos. Las propiedades calculadas se implementan de la manera esperada:

 const x = ref(5); const x_squared = computed(() => x.value * x.value); console.log(x_squared.value); // outputs 25

De manera similar, se pueden implementar varios tipos de relojes, métodos de ciclo de vida e inyección de dependencia. En aras de la brevedad, no los cubriremos aquí.

Supongamos que usamos el enfoque SFC estándar para el desarrollo de Vue. Incluso podríamos estar usando la API tradicional, con secciones separadas para datos, propiedades calculadas, etc. ¿Cómo integramos los pequeños fragmentos de reactividad de la API de composición con los SFC? Vue 3 presenta otra sección solo para esto: setup . La nueva sección se puede considerar como un nuevo método de ciclo de vida (que se ejecuta antes que cualquier otro gancho, en particular, antes de que se created ).

Aquí hay un ejemplo de un componente completo que integra el enfoque tradicional con la API de Composición:

 <template> <input v-model="x" /> <div>Squared: {{ x_squared }}, negative: {{ x_negative }}</div> </template> <script> import { ref, computed } from 'vue'; export default { name: "Demo", computed: { x_negative() { return -this.x; } }, setup() { const x = ref(0); const x_squared = computed(() => x.value * x.value); return {x, x_squared}; } } </script>

Cosas que sacar de este ejemplo:

  • Todo el código API de Composición está ahora en setup . Es posible que desee crear un archivo separado para cada funcionalidad, importar este archivo en un SFC y devolver los bits de reactividad deseados desde setup (para que estén disponibles para el resto del componente).
  • Puede mezclar el enfoque nuevo y el tradicional en el mismo archivo. Tenga en cuenta que x , aunque es una referencia, no requiere .value cuando se hace referencia en el código de la plantilla o en las secciones tradicionales de un componente como computed .
  • Por último, pero no menos importante, tenga en cuenta que tenemos dos nodos DOM raíz en nuestra plantilla; la capacidad de tener múltiples nodos raíz es otra característica nueva de Vue 3.

La reactividad es más expresiva en Vue 3

En la primera parte de este artículo, abordamos la motivación estándar de la API de composición, que es una organización y reutilización mejoradas del código. De hecho, el principal punto de venta de la nueva API no es su potencia, sino la comodidad organizativa que aporta: la capacidad de estructurar el código con mayor claridad. Puede parecer que eso es todo: que la API de composición permite una forma de implementar componentes que evita las limitaciones de las soluciones ya existentes, como los mixins.

Sin embargo, hay más en la nueva API. La API de composición en realidad permite no solo sistemas reactivos mejor organizados sino también más potentes. El ingrediente clave es la capacidad de agregar dinámicamente reactividad a la aplicación. Previamente, uno tenía que definir todos los datos, todas las propiedades calculadas, etc. antes de cargar un componente. ¿Por qué sería útil agregar objetos reactivos en una etapa posterior? En lo que queda, echamos un vistazo a un ejemplo más complejo: las hojas de cálculo.

Crear una hoja de cálculo en Vue 2

Las herramientas de hoja de cálculo como Microsoft Excel, LibreOffice Calc y Google Sheets tienen algún tipo de sistema de reactividad. Estas herramientas presentan al usuario una tabla, con columnas indexadas por A–Z, AA–ZZ, AAA–ZZZ, etc., y filas indexadas numéricamente.

Cada celda puede contener un valor simple o una fórmula. Una celda con una fórmula es esencialmente una propiedad calculada, que puede depender de valores u otras propiedades calculadas. Con hojas de cálculo estándar (y a diferencia del sistema de reactividad en Vue), estas propiedades calculadas incluso pueden depender de sí mismas. Tal autorreferencia es útil en algunos escenarios donde el valor deseado se obtiene por aproximación iterativa.

Una vez que cambia el contenido de una celda, todas las celdas que dependen de la celda en cuestión activarán una actualización. Si se producen más cambios, es posible que se programen más actualizaciones.

Si tuviéramos que crear una aplicación de hoja de cálculo con Vue, sería natural preguntar si podemos utilizar el propio sistema de reactividad de Vue y convertir a Vue en el motor de una aplicación de hoja de cálculo. Para cada celda, podríamos recordar su valor editable sin formato, así como el valor calculado correspondiente. Los valores calculados reflejarían el valor bruto si es un valor simple y, de lo contrario, los valores calculados son el resultado de la expresión (fórmula) que se escribe en lugar de un valor simple.

Con Vue 2, una forma de implementar una hoja de cálculo es tener raw_values , una matriz bidimensional de cadenas, y computed_values , una matriz bidimensional (computada) de valores de celda.

Si la cantidad de celdas es pequeña y fija antes de que se cargue el componente Vue apropiado, podríamos tener un valor bruto y otro calculado para cada celda de la tabla en nuestra definición de componente. Aparte de la monstruosidad estética que causaría tal implementación, una tabla con un número fijo de celdas en tiempo de compilación probablemente no cuente como una hoja de cálculo.

También hay problemas con la matriz bidimensional de computed_values . Una propiedad calculada es siempre una función cuya evaluación, en este caso, depende de sí misma (calcular el valor de una celda requerirá, en general, que ya se hayan calculado otros valores). Incluso si Vue permitiera las propiedades calculadas autorreferenciales, la actualización de una sola celda haría que todas las celdas se vuelvan a calcular (independientemente de si hay dependencias o no). Esto sería extremadamente ineficiente. Por lo tanto, podríamos terminar usando la reactividad para detectar cambios en los datos sin procesar con Vue 2, pero todo lo demás en cuanto a la reactividad tendría que implementarse desde cero.

Modelado de valores calculados en Vue 3

Con Vue 3, podemos introducir una nueva propiedad calculada para cada celda. Si la tabla crece, se introducen nuevas propiedades calculadas.

Supongamos que tenemos las celdas A1 y A2 , y deseamos que A2 muestre el cuadrado de A1 cuyo valor es el número 5. Un esquema de esta situación:

 let A1 = computed(() => 5); let A2 = computed(() => A1.value * A1.value); console.log(A2.value); // outputs 25

Supongamos que nos quedamos en este escenario simple por un momento. Hay un problema aquí; ¿Qué sucede si deseamos cambiar A1 para que contenga el número 6? Supongamos que escribimos esto:

 A1 = computed(() => 6); console.log(A2.value); // outputs 25 if we already ran the code above

Esto no cambió simplemente el valor 5 a 6 en A1 . La variable A1 ahora tiene una identidad completamente diferente: la propiedad calculada que se resuelve en el número 6. Sin embargo, la variable A2 aún reacciona a los cambios de la antigua identidad de la variable A1 . Entonces, A2 no debería referirse a A1 directamente, sino a algún objeto especial que siempre estará disponible en el contexto y nos dirá qué es A1 en este momento. En otras palabras, necesitamos un nivel de direccionamiento indirecto antes de acceder a A1 , algo así como un puntero. No hay punteros como entidades de primera clase en Javascript, pero es fácil simular uno. Si deseamos tener un pointer apuntando a un value , podemos crear un pointer = {points_to: value} . Redirigir el puntero equivale a asignar a pointer.points_to , y desreferenciar (acceder al valor apuntado) equivale a recuperar el valor de pointer.points_to . En nuestro caso procedemos de la siguiente manera:

 let A1 = reactive({points_to: computed(() => 5)}); let A2 = reactive({points_to: computed(() => A1.points_to * A1.points_to)}); console.log(A2.points_to); // outputs 25

Ahora podemos sustituir 5 por 6.

 A1.points_to = computed(() => 6); console.log(A2.points_to); // outputs 36

En el servidor Discord de Vue, el usuario redblobgames sugirió otro enfoque interesante: en lugar de usar valores calculados, use referencias que envuelvan funciones regulares. De esta manera, uno puede intercambiar la función de manera similar sin cambiar la identidad de la referencia en sí.

Nuestra implementación de hoja de cálculo tendrá celdas a las que se hace referencia mediante claves de algún arreglo bidimensional. Esta matriz puede proporcionar el nivel de direccionamiento indirecto que necesitamos. Por lo tanto, en nuestro caso, no necesitaremos ninguna simulación de puntero adicional. Incluso podríamos tener una matriz que no distinga entre valores brutos y calculados. Todo puede ser un valor calculado:

 const cells = reactive([ computed(() => 5), computed(() => cells[0].value * cells[0].value) ]); cells[0] = computed(() => 6); console.log(cells[1].value); // outputs 36

Sin embargo, realmente queremos distinguir los valores brutos de los calculados, ya que queremos poder vincular el valor bruto a un elemento de entrada HTML. Además, si tenemos una matriz separada para los valores brutos, nunca tendremos que cambiar las definiciones de las propiedades calculadas; se actualizarán automáticamente en función de los datos sin procesar.

Implementando la hoja de cálculo

Comencemos con algunas definiciones básicas, que en su mayor parte se explican por sí mismas.

 const rows = ref(30), cols = ref(26); /* if a string codes a number, return the number, else return a string */ const as_number = raw_cell => /^[0-9]+(\.[0-9]+)?$/.test(raw_cell) ? Number.parseFloat(raw_cell) : raw_cell; const make_table = (val = '', _rows = rows.value, _cols = cols.value) => Array(_rows).fill(null).map(() => Array(_cols).fill(val)); const raw_values = reactive(make_table('', rows.value, cols.value)); const computed_values = reactive(make_table(undefined, rows.value, cols.value)); /* a useful metric for debugging: how many times did cell (re)computations occur? */ const calculations = ref(0);

El plan es que cada computed_values[row][column] se calcule de la siguiente manera. Si raw_values[row][column] no comienza con = , devuelva raw_values[row][column] . De lo contrario, analice la fórmula, compílela en JavaScript, evalúe el código compilado y devuelva el valor. Para acortar las cosas, haremos un poco de trampa con fórmulas de análisis y no haremos algunas optimizaciones obvias aquí, como un caché de compilación.

Asumiremos que los usuarios pueden ingresar cualquier expresión de JavaScript válida como fórmula. Podemos reemplazar las referencias a los nombres de las celdas que aparecen en las expresiones del usuario, como A1, B5, etc., con la referencia al valor real de la celda (calculado). La siguiente función hace este trabajo, asumiendo que las cadenas que se asemejan a los nombres de las celdas realmente siempre identifican las celdas (y no son parte de alguna expresión de JavaScript no relacionada). Para simplificar, supondremos que los índices de columna constan de una sola letra.

 const letters = Array(26).fill(0) .map((_, i) => String.fromCharCode("A".charCodeAt(0) + i)); const transpile = str => { let cell_replacer = (match, prepend, col, row) => { col = letters.indexOf(col); row = Number.parseInt(row) - 1; return prepend + ` computed_values[${row}][${col}].value `; }; return str.replace(/(^|[^AZ])([AZ])([0-9]+)/g, cell_replacer); };

Usando la función transpile , podemos obtener expresiones de JavaScript puras a partir de expresiones escritas en nuestra pequeña "extensión" de JavaScript con referencias de celda.

El siguiente paso es generar propiedades calculadas para cada celda. Este procedimiento ocurrirá una vez en el tiempo de vida de cada celda. Podemos hacer una fábrica que devolverá las propiedades calculadas deseadas:

 const computed_cell_generator = (i, j) => { const computed_cell = computed(() => { // we don't want Vue to think that the value of a computed_cell depends on the value of `calculations` nextTick(() => ++calculations.value); let raw_cell = raw_values[i][j].trim(); if (!raw_cell || raw_cell[0] != '=') return as_number(raw_cell); let user_code = raw_cell.substring(1); let code = transpile(user_code); try { // the constructor of a Function receives the body of a function as a string let fn = new Function(['computed_values'], `return ${code};`); return fn(computed_values); } catch (e) { return "ERROR"; } }); return computed_cell; }; for (let i = 0; i < rows.value; ++i) for (let j = 0; j < cols.value; ++j) computed_values[i][j] = computed_cell_generator(i, j);

Si colocamos todo el código anterior en el método de setup , debemos devolver {raw_values, computed_values, rows, cols, letters, calculations} .

A continuación, presentamos el componente completo, junto con una interfaz de usuario básica.

El código está disponible en GitHub y también puede ver la demostración en vivo.

 <template> <div> <div>Calculations: {{ calculations }}</div> <table class="table" border="0"> <tr class="row"> <td></td> <td class="column" v-for="(_, j) in cols" :key="'header' + j" > {{ letters[j] }} </td> </tr> <tr class="row" v-for="(_, i) in rows" :key="i" > <td class="column"> {{ i + 1 }} </td> <td class="column" v-for="(__, j) in cols" :key="i + '-' + j" :class="{ column_selected: active(i, j), column_inactive: !active(i, j), }" @click="activate(i, j)" > <div v-if="active(i, j)"> <input :ref="'input' + i + '-' + j" v-model="raw_values[i][j]" @keydown.enter.prevent="ui_enter()" @keydown.esc="ui_esc()" /> </div> <div v-else v-html="computed_value_formatter(computed_values[i][j].value)"/> </td> </tr> </table> </div> </template> <script> import {ref, reactive, computed, watchEffect, toRefs, nextTick, onUpdated} from "vue"; export default { name: 'App', components: {}, data() { return { ui_editing_i: null, ui_editing_j: null, } }, methods: { get_dom_input(i, j) { return this.$refs['input' + i + '-' + j]; }, activate(i, j) { this.ui_editing_i = i; this.ui_editing_j = j; nextTick(() => this.get_dom_input(i, j).focus()); }, active(i, j) { return this.ui_editing_i === i && this.ui_editing_j === j; }, unselect() { this.ui_editing_i = null; this.ui_editing_j = null; }, computed_value_formatter(str) { if (str === undefined || str === null) return 'none'; return str; }, ui_enter() { if (this.ui_editing_i < this.rows - 1) this.activate(this.ui_editing_i + 1, this.ui_editing_j); else this.unselect(); }, ui_esc() { this.unselect(); }, }, setup() { /*** All the code we wrote above goes here. ***/ return {raw_values, computed_values, rows, cols, letters, calculations}; }, } </script> <style> .table { margin-left: auto; margin-right: auto; margin-top: 1ex; border-collapse: collapse; } .column { box-sizing: border-box; border: 1px lightgray solid; } .column:first-child { background: #f6f6f6; min-width: 3em; } .column:not(:first-child) { min-width: 4em; } .row:first-child { background: #f6f6f6; } #empty_first_cell { background: white; } .column_selected { border: 2px cornflowerblue solid !important; padding: 0px; } .column_selected input, .column_selected input:active, .column_selected input:focus { outline: none; border: none; } </style>

¿Qué pasa con el uso en el mundo real?

Vimos cómo el sistema de reactividad desacoplado de Vue 3 permite no solo un código más limpio, sino también sistemas reactivos más complejos basados ​​en el nuevo mecanismo de reactividad de Vue. Han pasado aproximadamente siete años desde que se introdujo Vue, y el aumento de la expresividad claramente no fue muy buscado.

El ejemplo de la hoja de cálculo es una demostración directa de lo que Vue es capaz de hacer ahora, y también puede ver la demostración en vivo.

Pero como un ejemplo de palabra real, es algo nicho. ¿En qué tipo de situaciones podría ser útil el nuevo sistema? El caso de uso más obvio para la reactividad bajo demanda podría estar en las ganancias de rendimiento para aplicaciones complejas.

Comparación de embudos Vue 2 vs Vue 3.

En las aplicaciones front-end que funcionan con una gran cantidad de datos, la sobrecarga de usar una reactividad mal pensada puede tener un impacto negativo en el rendimiento. Supongamos que tenemos una aplicación de panel de negocios que produce informes interactivos de la actividad comercial de la empresa. El usuario puede seleccionar un rango de tiempo y agregar o eliminar indicadores de rendimiento en el informe. Algunos indicadores pueden mostrar valores que dependen de otros indicadores.

Una forma de implementar la generación de informes es a través de una estructura monolítica. Cuando el usuario cambia un parámetro de entrada en la interfaz, se actualiza una sola propiedad calculada, por ejemplo, report_data . El cálculo de esta propiedad calculada se realiza de acuerdo con un plan codificado: primero, calcule todos los indicadores de rendimiento independientes, luego aquellos que dependen solo de estos indicadores independientes, etc.

Una mejor implementación desacoplará bits del informe y los calculará de forma independiente. Hay algunos beneficios para esto:

  • El desarrollador no tiene que codificar un plan de ejecución, lo cual es tedioso y propenso a errores. El sistema de reactividad de Vue detectará automáticamente las dependencias.
  • Dependiendo de la cantidad de datos involucrados, podríamos obtener mejoras sustanciales en el rendimiento, ya que solo estamos actualizando los datos del informe que lógicamente dependían de los parámetros de entrada modificados.

Si todos los indicadores de rendimiento que pueden formar parte del informe final se conocen antes de que se cargue el componente Vue, es posible que podamos implementar el desacoplamiento propuesto incluso con Vue 2. De lo contrario, si el backend es la única fuente de verdad (que es suele ser el caso de las aplicaciones basadas en datos), o si hay proveedores de datos externos, podemos generar propiedades calculadas bajo demanda para cada parte de un informe.

Gracias a Vue 3, esto ahora no solo es posible sino también fácil de hacer.