Qwik — Tasks / Lifecycles

Claves para entender el funcionamiento de las tareas y el ciclo de vida en proyectos Qwik para poder realizar aplicaciones eficientes y más rápidas

Anartz Mugika Ledo🤗
26 min readMay 17, 2023

Comenzamos con un nuevo artículo en el que vamos a ver un concepto super importante y que bajo mi punto de vista es esencial para subir de nivel en cualquier tecnología como en este caso en Qwik Framework, pero que también sería necesario entender en tecnologías como Angular, React, Vue,… en cada una de ellas con sus características pero como base, al final serían lo mismo.

Si entendemos el comportamiento del ciclo de vida de los componentes, ya tenemos un gran recorrido completado en lo que se refiere al aprendizaje de esa tecnología que estamos aprendiendo.

Por esa razón, os animo a que leáis bien todo el artículo, al detalle y que practiquéis sobre todo, algo fundamental para mejorar y asimilar los conceptos que se vayan a aprender aquí.

Os recomiendo leer los artículos anteriores en orden y si practicáis, ¡¡mucho mejor!!.

Recordad, que si hay dudas, aun leyéndolo, podéis preguntarlo en los comentarios y sin dudas me gustaría que me comentéis también, para tener feedback.

Todos los artículos publicados del curso los encontraréis en la siguiente lista que iré actualizando semanalmente y estableciendo el orden natural recomendado:

Qwik paso a paso desde 0 al detalle

23 stories

En este artículo trabajaremos con la versión 1.1.0, que es la versión estable que tenemos en la actualidad (17/05/2023).

Para comenzar el artículo, debemos de tener el proyecto creado.

Índice de contenidos

  • ¿Qué es un ciclo de vida? ¿Qué nos aporta?
  • Las tareas que se ejecutan dentro de Qwik.
  • Ciclos de vida en Qwik. Introducción.
  • useTask$()
  • useVisibleTask$()

¿Qué es un ciclo de vida? ¿Qué nos aporta?

Los ciclos de vida de un componente son un tema fundamental en el diseño y la ingeniería de productos.

Estos ciclos describen las diferentes etapas que atraviesa un componente desde su concepción hasta su retiro y desecho.

Comprender estos ciclos es esencial ya que nos van a aportar la garantía de calidad y la confiabilidad de los productos, así como nos ayudarán a maximizar su vida útil y reducir su impacto ambiental.

En este sentido, en este artículo vamos a explorar los diferentes aspectos relacionados con los ciclos de vida de los componentes, desde su diseño y desarrollo hasta su mantenimiento, reparación y eliminación.

Tareas que se ejecutan dentro de Qwik

Para poder abordar el tema de los elementos que usaremos para gestionar el ciclo de vida de una aplicación de Qwik, es importante entender como funciona la parte de tareas en este framework.

En Qwik, las tareas están diseñadas para ejecutar operaciones asíncronas como parte de la inicialización del componente o del cambio de estado del componente.

Para los que vienen de React y tienen conocimientos sobre los hooks, podemos decir que estas tareas son similares a useEffect() en React, pero al haber suficientes diferencias no sería correcto llamarlos de la misma forma. Las principales diferencias son:

- Las tareas son asíncronas.

- Las tareas se ejecutan tanto en el servidor y en el navegador.

- Las tareas se ejecutan antes de renderizar y pueden bloquear la renderización.

useTask$() debería ser siempre nuestra primera opción para ejecutar trabajos asíncronos (o sincrónicos) como parte de la inicialización o del cambio de estado del componente.

Habrá casos en los que no se puede lograr lo que se necesita con useTask$(), y ahí es donde se debe considerar el uso de useVisibleTask$() o useResource$() dependiendo de la necesidad del momento.

El caso de uso básico para useTask$() es realizar un trabajo en la inicialización del componente.

useTask$() tiene estas principales propiedades que debemos de tener en cuenta:

  • Puede ejecutarse en el servidor o en el navegador.
  • Se ejecuta antes de la renderización y bloquea la renderización.
  • Si se están ejecutando varias tareas, se ejecutan secuencialmente en el orden en que se registraron. Una tarea asíncrona bloqueará la siguiente tarea hasta que se complete.

Las tareas también se pueden utilizar para realizar trabajo cuando cambia el estado del componente.

En este caso, la tarea se volverá a ejecutar cada vez que cambie el estado rastreado mediante el uso de la función track() que la veremos con más detalle dentro de este artículo, ya que es un elemento muy muy muy (si hago hincapié al muy, no es un error) importante que va a ser protagonista de nuestros proyectos de Qwik desde proyectos básicos a más avanzados.

¿Cuándo no usar useTask$?

Un buen ejemplo es cuando una tarea solo necesita ejecutarse en el navegador y después de la renderización, en ese caso, se debe utilizar useVisibleTask$().

Este punto lo veremos con más detalle a lo largo del artículo que será como mostrar algo visual como un Mapa con la librería de Leaflet que no actúará de ninguna manera en el servidor, solo en el navegador.

Podéis ver un artículo que escribí acerca de esto último. Lo que tenéis que tener en cuenta que habría que sustituir useBrowserVisibleTask$() por el actual useVisibleTask$()

El ejemplo que usaremos en el artículo para explicar este caso será el de renderizar la hora actual con un reloj. Lo veremos hacia el final, paso a paso.

También hay casos como el de consumir una API remota, donde debemos recuperar los datos de forma asíncrona y producir una señal (sin bloquear la renderización).

En este caso particular, deberemos de utilizar useResource$() visto en un artículo anterior donde trabajaba consumiendo APIs REST y GraphQL.

Como podéis observar, aparte de este contenido tenemos apoyo de otras informaciones creadas por mi, para poder saber trabajar con estas opciones también, os invito a visualizarlas, estudiarlas y aplicarlas en vuestros proyectos. También que lo compartáis obviamente, que es GRATIS y ayudaría mucho a expandirlo, para que ayude a mucha gente.

Ciclos de vida en Qwik.

Gracias a la resumabilidad, la vida útil de un componente y su ciclo de vida se extienden a través del tiempo de ejecución del servidor y del navegador.

Nota: Resumabilidad: La capacidad de reanudación (Resumability) permite que las aplicaciones de Qwik continúen ejecutándose donde las dejó el servidor (Explicado con más detalles y ejemplos en el primer artículo de la serie de artículos sobre Qwik)

A veces, el componente se renderiza primero en el servidor y otras veces se va a renderizar en el navegador.

Sin embargo, en ambos casos, el ciclo de vida (orden) será el mismo, solo que su ubicación de ejecución será en diferentes entornos (servidor VS navegador).

A raíz de esto, es bueno recordar que para los sistemas que utilizan la hidratación (Angular, React, Vue,…entre otros), la ejecución de la aplicación ocurre dos veces.

Una vez en el servidor (SSR/SSG) y otra vez en el navegador (hidratación). Por esta razón, muchos frameworks tienen “efectos” que solo se ejecutan en el navegador. Eso significa que el código que se ejecuta en el servidor es diferente al código que se ejecuta en el navegador.

En cambio, la ejecución de Qwik está unificada, lo que significa que si el código ya se ha ejecutado en el servidor, no lo va a volver a ejecutar en el navegador.

Ahora ya teniendo claro el funcionamiento básico de los ciclos de vida y tareas de estas en los componentes de Qwik, vamos a ver con más detalle las etapas que componen un ciclo de vida en Qwik, que serán únicamente tres.

Etapas que componen un ciclo de vida en Qwik

En este apartado vamos a poder entender el proceso que componen las etapas para completar el ciclo de vida en un componente de Qwik.

  1. Task (Tarea) — useTask$()— se ejecuta antes del renderizado (En servidor) y también cuando cambian los estados rastreados (Ya en el navegador, con cualquier cambio de estado). Las tareas se van a ejecutar en orden, de manera secuencial y bloquean el renderizado.
  2. Render (Renderizado )— se ejecuta después de Task y antes de Visible Task.
  3. Visible Task(Tarea Visible) — useVisibleTask$() — se ejecuta después del Renderizado y cuando el componente se vuelve visible.

A continuación os muestro de manera visual su funcionamiento principal con el proceso del ciclo de vida completo:

Servidor (Server)

Por lo general, la vida de un componente comienza en el servidor (durante SSR o SSG), por lo que en este caso, useTask$() y RENDER se ejecutarán en el servidor, y luego Visible Task con useVisibleTask$() se ejecutará en el navegador, después de que se obtenga la validación que dicho componente está ya visible.

Tenéis que tener en cuenta que en este caso debido a que el componente se ha montado en el servidor, solo useVisibleTask$() se ejecuta en el navegador. Esto se debe a que el navegador continúa el mismo ciclo de vida que se pausó en el servidor justo después del renderizado y se reanudó en el navegador. Por lo tanto, primero superar la etapa del servidor y cuando ya esté todo OK, pasa al navegador.

Navegador (Browser)

A veces, un componente se puede montar / renderizar primero en el navegador.

¿En qué casos ocurrirá esto? Aquí unos ejemplos:

  • Usuario navega en SPA a una nueva página.
  • Un componente “modal” aparece por primera vez en la página.
  • Cualquier cambio de estado que estemos observando con la función track

En esta situación, el ciclo de vida se ejecutará de la siguiente manera:

Como podéis observar, en este caso también se ejecuta useTask$(), y aunque el ciclo de vida es el mismo, al ser tareas únicamente del navegador, no hace participe a la parte del servidor y solo se ejecuta en el navegador.

Pensad que a estas alturas, ya se ha hecho antes el primer renderizado completo pasando por todo el el proceso de la etapa de servidor -> Renderizado -> Navegador, solo que ahora ya estamos ejecutando ciertas tareas que solo requieren la participación del navegador, sin hacer participe al servidor, por no ser necesario.

Ahora que ya hemos diferenciado un poco el proceso del ciclo de vida, es conveniente entender como funcionan los hooks useTask$() y useVisibleTask$()en las diferentes situaciones.

En estos momentos seguramente tenéis dudas, y es normal. No os preocupéis, ya que abordando de primera mano la teoría, a veces suele ser más difícil verlo claro. Por eso, aparte de los detalles que voy a dar sobre cada uno de estos hooks, daré ejemplos de código real, para que se pueda ver su funcionamiento de una manera más clara.

¡¡Vamos a por ello!!

useTask$

El hook useTask$() nos va a permitir ejecutar código en el proceso de crear el componente, antes del renderizado inicial, es decir, desde el servidor y mínimo una vez.

Opcionalmente, si le pasamos el parámetro track, nos va a permitir ejecutar dicho código también cada vez que el valor que se está observando cambie mediante la reactividad. En esta situación ya no requiere la participación del apartado del servidor debido a que la reactividad es algo exclusivo del navegador.

Este punto, el de observar los cambios con track, lo vamos a tratar en breves dentro de este artículo con mucho más detalle

En resumen, esto sería lo que tenemos que meternos en la cabeza, ya que es fundamental tener claro estos puntos:

  • ¿Cuándo?: ANTES del primer renderizado del componente (Servidor => RENDER => NAVEGADOR) y cuando cambian los estados rastreados (NAVEGADOR).
  • ¿Cuántas veces?: mínimo una vez
  • ¿En qué plataformas?: Se dará tanto en el apartado del servidor como en el navegador.

useTask$() es un método muy útil para iniciar nuestros componentes en Qwik pero como se activa desde el servidor, todavía no tendremos acceso al window o al document, por lo que para acceder a elementos del DOM cuando se va a renderizar el componente, deberemos utilizar useVisibleTask$() que veremos con más detalle más adelante en este artículo.

Ahora que ya hemos entendido el funcionamiento de este hook en lo que se refiere al aspecto teórico, vamos a ver al detalle en las diferentes variantes disponibles teniendo en cuenta estos apartados con casos reales que es la forma de verlo más claro:

  • useTask$() sin observar cambios, sin añadir track()
  • track() escuchando posibles cambios
  • track() escuchando cambios y trabajando como función
  • cleanup()

a) useTask$() sin observar cambios — sin track

En esta situación lo que hará este hook básicamente es montar un componente, nada más.

En Qwik no existe un hook específico para el montaje de un componente, ya que el hook useTask$(), sin seguimiento, se comporta efectivamente como un hook de montaje (hook OnMount).

Esto se debe a que useTask$() se ejecuta al menos una vez cuando el componente se monta por primera vez. En este caso SOLO SE EJECUTA UNA VEZ.

El código sería algo como lo siguiente:

import { component$, useTask$ } from '@builder.io/qwik';

export default component$(() => {

useTask$(async () => {
// Tarea sin observar el estado con `track` simulando el hook OnMount.
console.log(
'Esto se ejecuta cada vez que se monta el componente en Servidor o Navegador'
);
});

return <div>Probando useTask$() sin observar cambios - Simulando OnMount</div>;
});

El resultado se mostraría en la consola y no habrá cambios, será este el mensaje que se muestre en la consola del servidor:

'Esto se ejecuta cada vez que se monta el componente en Servidor o Navegador'

Recordad que un aspecto único de Qwik es que los componentes se montan solo UNA VEZ en el servidor y en el cliente. Esto es una propiedad de la resumabilidad. Lo que significa es que si useTask$() se ejecuta durante SSR, no se volverá a ejecutar en el navegador porque Qwik no hidrata.

b) useTask$() observando los cambios —con track

Hay momentos que tenemos la necesidad de volver a ejecutar una tarea cuando cambia el estado de un componente. Esto lo vamos lograr utilizando la función track() que nos va a permitir re-ejecutar ese hook nuevamente.

La función track() permite establecer una dependencia en el estado de un componente en el servidor (si se ha renderizado inicialmente allí) y luego volver a ejecutar la tarea cuando cambia el estado en el navegador (siempre en el navegador, nunca en el servidor).

Os voy a mostrar un ejemplo con un pequeño contador usando useSignal(), para ir modificando su estado a medida que hacemos click al botón de +1 mostrándose en todo momento el valor actualizado.

El código os lo añado aquí con los diferentes apartados comentados, como extra para entender algunos conceptos nuevos y repasar otros ya vistos anteriormente:

import { component$, useSignal, useTask$ } from '@builder.io/qwik';
import { isServer } from '@builder.io/qwik/build';

export default component$(() => {
// (1) Iniciamos el estado del contador a 0
const counter = useSignal(0);

// (2) Añadimos la tarea, que en este caso lo haremos observando
// Los cambios del valor de counter, mediante track(() => counter.value)
useTask$(async ({ track }) => {
// (3) Tarea observando el estado con `track` del valor counter.value.
// En la primera renderización se suscribe a escuchar cambios
track(() => counter.value);

// (4) Log que se muestra la primera renderización, desde el servidor

// y en cada re-ejecución por cambio en el valor observado (counter.value)
console.log(
'Esto se ejecuta cada vez que se monta el componente en Servidor o Navegador'
);

// 5.- Comprueba si no está ejecutándose del servidor para mostrar el registro
!isServer &&
console.log(
`Contador (${counter.value}) - Re-ejecución dada por click en +1, esto se muestra en consola del navegador`
);
});
return (
<>
<div>Probando useTask$() observando cambios</div>
<button onClick$={() => counter.value++}>+1</button>
<h2>Valor actual: {counter.value}</h2>
</>
);
});

Después de añadir ese código y guardar los cambios, comenzamos con el ciclo de vida.

ETAPA 1: Renderizado inicial desde el servidor.

Al cargar el proyecto en el navegador, esto se verá de la siguiente forma:

Y lo que se visualiza en las consolas es lo siguiente, tanto en Servidor como Navegador:

Como podéis observar en la consola del servidor se muestra el mensaje Esto se ejecuta cada vez que se monta el componente en Servidor o Navegador pero no en la consola del navegador.

¿Cómo se ha llegado a este punto? ¿Cuáles han sido los pasos dados?

En este punto, ha seguido este proceso:

  • Ejecuta useTask$() desde el servidor, mientras tanto bloquea del todo la renderización.
  • Dentro de useTask$() se suscribe a observar el valor de counter.value
  • En el navegador ahora se muestran mensajes que vienen por defecto en Qwik.
  • Finaliza la tarea, desbloquea el proceso y renderiza el contenido completando la primera renderización, por lo que las notificaciones las vamos a recibir a partir de la acción de click +1.

Pulsamos +1 para cambiar el valor del contador y pasar a la etapa 2:

ETAPA 2: Re-ejecución de useTask$() desde navegador

Y en el momento de ejecutarse, ocurre lo siguiente:

  1. Se re-ejecuta la función useTask$() ya que está observando los cambios de counter.value con la función track().
  2. Muestra el primer mensaje (Esto se muestra cada vez que se ejecuta useTask, al inicio o en una re-ejecución) que se mostraba antes, pero ahora en vez de hacerlo en la consola del servidor, lo hace en el navegador.
  3. Muestra el segundo log, ya que counter.value ya se está ejecutando en el navegador.
  4. Renderiza el valor sustituyendo el valor 0 por 1.

Después de la acción de click , lo modificado en el valor del contador, dejará su estado así:

Y lo registrado en la consola, será lo siguiente:

  1. El mensaje que se había registrado en el registro de la consola del Servidor, sigue estando el mismo, no ha cambiado nada.
  2. El mensaje Container Resumed es para especificar que ha hecho la primera-re-ejecución. Ahora si se diesen más cambios, este mensaje no aparecería, solo aparece en la primera re-ejecución al cambiar el estado desde el navegador.
  3. Este es el mensaje que se ha mostrado en la primera renderización en la consola del servidor, pero ahora lo hace en el navegador
  4. Mensaje con el valor del contador actual, que se muestra solo en el navegador.
  5. Este mensaje, con Render Stats, es un registro que se ejecuta cuando cambia el estado de un valor que está añadido en el código JSX. En el caso con el que estamos trabajando, está el valor counter.value dentro de un h2. Si hacemos click en la flecha que está junto con ese mensaje, si vamos a operations, ahí podemos ver toda la información de lo que ha modificado en el DOM. En este caso, como es un valor el que se modifica, tenemos un elemento como se puede ver a continuación poniendo en cursor sobre text.

Y ahora que ya hemos re-ejecutado, seguro que os lo preguntáis ¿Sería posible hacerlo de nuevo?

Si sería posible, todas las veces que queramos y en este caso, el resultado del proceso será el mismo que lo que acabamos de describir, todo se irá registrando en la consola del navegador. Probando con dos pulsaciones al botón (ya teníamos counter.value que era 1) llegaremos al valor 3, como se puede ver en el penúltimo mensaje registrado en la consola del navegador:

En este apartado he detallado bastante, pero creo que es super importante identificar todos estos detalles, para poder identificar las cosas, ante posibles dudas de estos eventos.

Pasamos al uso de track(), como función, para modificar valores o extraer información en una variable.

c) useTask$() observando los cambios — con track trabajando como función

En el punto anterior, hemos usado track() para rastrear una señal concreta. Sin embargo, track() también lo podemos utilizar como una función para rastrear múltiples señales a la vez.

¿Cómo lo hacemos? Es muy sencillo, usando la base anterior podemos hacer que se compute otro valor, haciendo un funcionamiento muy similar a useComputed$(), solo que en vez de hacerlo fuera de la función track(), lo hacemos dentro de track()

Usando como base el ejemplo anterior, nos centramos en la parte donde vamos a añadir un nuevo valor con el hook useSignal que llamaremos result, donde dependiendo del valor original del valor de counter, evaluaremos si es un valor par o impar y dependiendo de eso, en el result almacenaremos el mensaje correspondiente.

  • Si counter.value es PAR => El valor ${ counter.value } es PAR
  • Si counter.value es IMPAR => El valor ${ counter.value } es IMPAR

Teniendo en cuenta esto, cambiamos el código correspondiente de lo que teníamos en el punto anterior, a lo siguiente, haciendo hincapié en los apartados que he hecho cambios:

import { component$, useSignal, useTask$ } from '@builder.io/qwik';

export default component$(() => {
const counter = useSignal(0);
// (1) Añadimos el valor para hacer los cambios con el resultado de counter
const result = useSignal(``);

useTask$(async ({ track }) => {
// (2) Tarea observando el estado con `track` del valor counter.value
// y a su vez se usa para comprobar su valor y asignar el resultado
// si es PAR o IMPAR
track(() => {
result.value = `El valor ${counter.value} es ${
counter.value % 2 === 0 ? 'PAR' : 'IMPAR'
}`;
});

// Hacemos un pequeño cambio para que se muestre el valor 0 del contador
// en el registro de la consola del servidor
console.log(
`${isServer ? 'SERVIDOR' : 'NAVEGADOR'}: Contador (${counter.value}) ${
!isServer
? '- Re-ejecución dada por click en +1, esto se muestra en consola del navegador'
: ''
}`
);


// (3) Añadimos abajo el h4 para visualizar el resultado
});

return (
<>
<div>Probando useTask$() observando cambios con use de función</div>
<button onClick$={() => counter.value++}>Click para +1</button>
<h3>Valor actual: {counter.value}</h3>
<h4>Resultado: {result.value}</h4>
</>
);
});

Guardamos los cambios y empezamos de nuevo el proceso mediante las diferentes etapas que se darán desde el inicio.

Etapa 1: Primera ejecución de useTask$()

Una vez efectuados los cambios, guardamos y vemos el resultado de lo que da el primer renderizado (Recordad, entrando en useTask$() como se ha explicado anteriormente), tanto en los registros de consola como en la aplicación.

En la aplicación tenemos la siguiente apariencia:

Y en la consola, se muestra lo siguiente, como veis, el funcionamiento de la etapa 1 en este caso es el mismo, ya que no ha cambiado nada en ese aspecto:

Ahora, cuando hagamos click, va a comportarse parecido, solo que en vez de renderizar solo el valor de counter.value, va a asignar un nuevo valor a result.value en base al cambio en counter.value y eso hará que se muestren los nuevos cambios.

Etapa 2: Re-ejecución de useTask$()

Hacemos un click en +1 para dispara la re-ejecución y ahora, aparte de hacer +1, pasando de 0 a 1, comprobará el valor de counter.value si es múltiplo de dos y como no lo es, nos dirá que es impar con su correspondiente mensaje, mostrando el siguiente resultado en la aplicación:

Y esto sería el resultado de como se refleja en el registro de la consola tanto del Servidor como del navegador.

En el primero (1) sigue estando la misma información como hemos visto en el punto anterior y en el segundo, los tres primeros registros (2) es lo mismo que en el punto anterior y el último (3), es prácticamente lo mismo a excepción de que en operations, ahora en vez de ser un elemento que se ha renderizado, han sido dos, correspondiente a counter.value y result.value. Os animo a que despleguéis y hagáis la misma jugada que hemos hecho antes.

Con esto ya tenemos este punto finalizado, pasamos al último donde vamos a aprender hacer uso de cleanup.

d) cleanup

A veces, al ejecutar una tarea, es necesario realizar trabajo de limpieza. Cuando se desencadena una nueva tarea, se invoca la devolución de llamada cleanup() de la tarea anterior.

También se invoca la devolución de llamada cleanup() cuando el componente se elimina del DOM.

A tener en cuenta sobre cleanup():

  • NO SE INVOCA cuando se completa la tarea. Solo se invoca cuando se desencadena una nueva tarea o cuando se elimina el componente.
  • SE INVOCAN en el servidor después de que las aplicaciones se serializan en HTML.
  • NO ES TRANSFERIBLE del servidor al navegador. (La limpieza está destinada a liberar recursos en la máquina virtual donde se está ejecutando. No está destinada a transferirse al navegador).

Con estos datos, vamos a verlo aplicado en un ejemplo real, haciendo uso de un timer, para mostrar un texto, a los 1500ms (1.5 segundos) después de cambiar la selección del elemento de un array que pondremos con una lista de valores.

El código es el siguiente donde iré añadiendo los comentarios de las partes que se van implementando:

import { component$, useSignal, useTask$ } from '@builder.io/qwik';

export default component$(() => {
// 1.- Lista de los valores que tenemos que se irán mostrando según posición
const jsTechnlogies = ['Qwik Framework', 'Astro', 'Angular', 'React', 'Vue'];

// 2.- Posición actual para seleccionar el valor de la lista
const selectElement = useSignal(0);

// 3.- Texto que se usará como duplicado que aparecerá al 1.5sg de cambiar
// el valor de selectElement
const debounceText = useSignal('');

// 4.- Función con track y el nuevo elemento, cleanup
useTask$(({ track, cleanup }) => {
// 5.- Observamos los cambios de selectElement y usamos para seleccionar
// de la lista el valor del array
const value = track(() => jsTechnlogies[selectElement.value]);

// 6.- Iniciar el timer para asignar "value" en debounceText al pasar 1.5sg
const id = setTimeout(() => {
console.log('Asignado el texto de la tecnología');
debounceText.value = value;
}, 1500);

// 7.- Se ejecutará para limpiar el timerId iniciado al terminar la tarea
// o al cancelarla
cleanup(() => {
console.log('Entrado a limpiar timer');
clearTimeout(id);
});
});

// 8.- El código que se va a renderizar
return (
<section>
<label>Tecnología: {jsTechnlogies[selectElement.value]}</label>
<br />
<br />
<button
onClick$={() => {
// 9.- Seleccionamos el valor para seleccionar texto.
// Si llega a 4, se pasa a 0 en el siguiente click.
selectElement.value =
selectElement.value === 4 ? 0 : selectElement.value + 1;
}}
>
Click - Seleccionar nueva tecnología
</button>
<p>Retardo (1500ms - 1.5sg) - Tecnología: {debounceText}</p>
</section>
);
});

Etapa 1 (Servidor): Primera ejecución de useTask$()

Este será el primer renderizado, nada nuevo de lo que ya hayamos visto antes.

Y esto es lo que se refleja en el registro de la consola del servidor y el navegador:

Como se puede ver, se muestra el mensaje Entrado a limpiar timer en el registro de la consola del servidor, al haber completado la tarea ya que ha pasado al navegador, desde el primer renderizado realizado desde el servidor.

Este mensaje también se puede mostrar si cancelamos su ejecución antes de tiempo.

Etapa 2 (Navegador): Primera re-ejecución de useTask$()

Probamos primero ejecutando mediante el click al botón y esperando (1.5 segundos), ahora debería de mostrarse lo siguiente en el registro de la consola del navegador (en servidor no cambia). Ahora como bien se ha visto, se renderiza desde el navegador.

  1. Sin cambios, con el mensaje inicial.
  2. Acción para cambiar y seleccionar una nueva tecnología.
  3. Asociado a 2. Se ejecuta Container Resumed como primer renderizado y se añade el registro asociado a la renderización para el 4.
  4. Valor que se renderiza a partir de seleccionar nueva tecnología.
  5. Valor que se muestra en el registro de la consola del navegador al pasar 1.5 segundos. A su vez se renderiza el valor en la aplicación con debounceText.value
  6. Texto que se renderiza al pasar 1.5 segundos junto con el registro en consola

Etapa 3 (Navegador): Segunda y tercera re-ejecución de useTask$()

Ya hemos hecho el primer click, se ha re-ejecutado, completando todo el proceso, esperando los 1.5 segundos (ETAPA 2 Finalizada, del paso anterior)

Lo que vamos a hacer ahora es forzar, una nueva re-ejecución dentro de otra.

Como tenemos que esperar 1.5 segundos a que muestre el valor debounceText.value, pulsaremos el botón una vez y antes de que supere los 1.5 segundos, pulsamos de nuevo (ETAPA 3 — Se cancela por click) a propósito para seleccionar otro valor .

Con esto, iniciará una nueva ejecución a useTask$(), ignorando lo que se iba a mostrar con el primer click antes de pulsar de seguido de nuevo consiguiendo que después del intervalo que tiene que pasar para renderizar (1.5 segundos) llegue a su fin, completando toda la tarea correctamente.

Este proceso se visualizará de la siguiente forma:

Con esto, llegamos al final de todo lo que hay que saber sobre useTask$() en el proceso de los ciclos de vida de un componente de Qwik.

useVisibleTask$

A veces, una tarea solo debe ejecutarse en el navegador y ya después de la renderización.

En ese caso, se debe utilizar useVisibleTask$().

useVisibleTask$() es similar a useTask$(), pero solo se ejecuta en el navegador y después de la renderización inicial.

Su funcionamiento es el siguiente:

  • useVisibleTask$() registra un hook que se ejecutará cuando el componente sea visible en el viewport, que es la parte del documento (document) que nosotros estaremos viendo, haciendo referencia a que ya es visible.
  • Se ejecutará al menos una vez en el navegador y puede ser reactivo y volver a ejecutarse cuando cambie algún estado rastreado como hemos visto anteriormente con useTask$() con la función track().

En resumen, podemos decir que tiene estas propiedades:

  • Se ejecuta solo en el cliente.
  • Ejecuta el código que está disponible en el cliente cuando el componente se vuelve visible (por defecto).
  • Se ejecuta después de la renderización inicial.
  • No bloquea la renderización a diferencia de useTask$() que si lo hace.

Ejemplo para entender este hook de como se ejecuta

Para ver el funcionamiento de este hook, vamos a añadir el código donde tendremos una pantalla con un componente llamado Clock que no se encontrará visible en el navegador y que se encontrará pausado, mientras no hagamos scroll y lo visibilicemos.

Para ir construyendo el componente, lo primero que hacemos es lo siguiente:

import {
component$,
useSignal,
} from '@builder.io/qwik';

export default component$(() => {
// 1
const isClockRunning = useSignal(false);

return (
<>
// 2
<div style="position: sticky; top:0">
Haz scroll para poder ver el reloj. (El reloj se encuentra:
{isClockRunning.value ? ' EN MARCHA' : ' NO ESTÁ EN MARCHA'}.)
</div>
// 3
<div style="height: 200vh" />
// 4
<p>AQUÍ PONDREMOS EL COMPONENTE DEL RELOJ</p>
</>
);
});

Explicando al detalle los elementos del componente principal, sin tener en cuenta el componente Clock:

  1. Estado donde se almacenará el valor de si el reloj está en marcha o no. Por defecto es false, ya que el reloj no estará visible y por lo tanto no estará activo esto.
  2. Cabecera con las instrucciones a realizar y muestra del estado del reloj, que al inicio está en estado NO ESTÁ EN MARCHA.
  3. Añadimos un div aplicando una altura del doble de lo que ocupa la pantalla con height: 200vh para asegurarnos que tenemos que hacer scroll hasta visibilizar el componente Clock. Por ejemplo, si la altura del pantalla es de 500 píxeles, un elemento con “height: 200vh” tendría una altura de 1000 píxeles.
  4. Este apartado, lo vemos ahora, creando un nuevo componente llamado Clock, donde se muestra el contenido de un reloj en marcha.

Ahora que ya tenemos disponible el componente principal, nos hace falta crear el componente Clock, que será el componente que nos muestra la hora y que mientras no lo tengamos visible, estará pausado.

El código de este componente será lo siguiente:

import {
component$,
Signal,
useSignal,
useVisibleTask$,
} from '@builder.io/qwik';
...
const Clock = component$<{ isRunning: Signal<boolean> }>(({ isRunning }) => {
// 1
const time = useSignal('El reloj se va a poner en marcha...');

// 2
useVisibleTask$(({ cleanup }) => {
console.log('CLOCK - ¡¡Esta parte es visible!!');

// 3
isRunning.value = true;

// 4
const update = () => (time.value = new Date().toLocaleTimeString());

// 5
const timerId = setInterval(update, 1000);

// 6
cleanup(() => clearInterval(timerId));
});

// 7
return <div>{time.value}</div>;
});

Explicación de los apartados del código del componente Clock:

  1. Valor que será el que almacene el texto de la hora actual, cuando el reloj esté en marcha al visibilizar este componente.
  2. Hook que se activa cuando se visualiza el componente.
  3. Actualizamos el valor para notificar al componente principal que está en marcha el reloj.
  4. Creamos la función que actualizará en time.value usando la hora actual local.
  5. Añadimos la función del punto anterior, para que se ejecute cada segundo, simulando un reloj de la vida real.
  6. Función para limpiar el flujo del timer que usamos, tal y como se ha trabajado anteriormente.
  7. Mostramos la hora almacenada en time.value

Y con todo esto que hemos visto, este será el resultado final del código implementado:

import {
component$,
Signal,
useSignal,
useVisibleTask$,
} from '@builder.io/qwik';

export default component$(() => {
const isClockRunning = useSignal(false);

return (
<>
<div style="position: sticky; top:0">
Haz scroll para poder ver el reloj. (El reloj se encuentra:
{isClockRunning.value ? ' EN MARCHA' : ' NO ESTÁ EN MARCHA'}.)
</div>
<div style="height: 200vh" />
<div style="border: 6px dotted yellow">
<Clock isRunning={isClockRunning} />
</div>
</>
);
});

const Clock = component$<{ isRunning: Signal<boolean> }>(({ isRunning }) => {
const time = useSignal('El reloj se va a poner en marcha...');
useVisibleTask$(({ cleanup }) => {
console.log('CLOCK - ¡¡Esta parte es visible!!');
isRunning.value = true;
const update = () => (time.value = new Date().toLocaleTimeString());
const timerId = setInterval(update, 1000);
cleanup(() => clearInterval(timerId));
});
return <div>{time.value}</div>;
});

Al guardar, este será el estado inicial de nuestra aplicación:

  1. NO ESTÁ EN MARCHA por ser isClockRunning = false
  2. Nos pide que hagamos scroll
  3. Hacemos scroll y scroll, sin parar.
  4. Como podéis ver, no muestra el mensaje CLOCK — ¡¡Esta parte es visible!! que sería el registro para decirnos que está visible y se pondrá en marcha.

Siguiendo las indicaciones, hacemos scroll hasta que veamos un elemento con bordes amarillos y punteados, que será señal de que estamos visibilizando el componente CLOCK.

Al visualizar el reloj ha ocurrido lo siguiente:

  1. El reloj se pone en marcha, cambia el mensaje NO ESTÁ EN MARCHA por ESTÁ EN MARCHA asignándose a true el valor isClockRunning
  2. Se dispara la ejecución del hook useVisibleTask$(), poniendo en marcha el reloj, que hará un renderizado cada segundo que se irá registrando como se ve en 3 y 4.
  3. Junto con 4, es el renderizado con el nuevo valor de la hora que se visualiza en la pantalla y esto estará en marcha hasta que nosotros vayamos a otra pantalla.

Aquí ya podemos ver el uso básico de este Hook.

Opción eagerness

A veces es deseable ejecutar useVisibleTask$() tan pronto como se carga la aplicación en el navegador.

En este caso particular, debemos de ejecutar el hook useVisibleTask$() en modo eagerness.

Esto se va a lograr utilizando la opción {strategy: "document-ready"} que se reflejará en el código utilizado anteriormente de la siguiente forma, sin muchos cambios:

import {
component$,
Signal,
useSignal,
useVisibleTask$,
} from '@builder.io/qwik';

export default component$(() => {
...(SIN CAMBIOS)...
});

const Clock = component$<{ isRunning: Signal<boolean> }>(({ isRunning }) => {
const time = useSignal('El reloj se va a poner en marcha...');
useVisibleTask$(
...(SIN CAMBIOS)...
},
{ strategy: 'document-ready' } // <===== EL CAMBIO
);
return <div>{time.value}</div>;
});

Esta opción eagerness, cuya traducción al español podría ser impaciente, hace honor a esa impaciencia, que en vez de poner en marcha el componente cuando se visibilice, lo haremos desde el momento que se tengan las condiciones para ponerlo en marcha, en este caso desde que ya tiene disponible los elementos del navegador

Guardando los cambios y ejecutando nuestra aplicación:

Vemos un cambio importante, es que ahora el reloj si está EN MARCHA, desde el primer momento y no hemos hecho scroll.

¿Qué pasará si vamos a la consola? (seguimos sin hacer scroll como antes hasta visibilizar el reloj)

En efecto, está renderizando y mostrando los registros segundo a segundo por estar el reloj en marcha.

Si hacemos scroll, veremos que el reloj ya está en marcha desde hace un buen rato:

Esto en términos de rendimiento no creo que sea la mejor opción de primeras pero todo depende de las necesidades del proyecto.

Con esto, podremos decir que ya hemos visto todo lo indispensable para trabajar con los ciclos de vida de Qwik con sus diferentes opciones y variantes.

Conclusión

Y llegados a este punto, con todo lo que hemos estudiado deberíamos de entender todas las claves y cosas a tener en cuenta con los ciclos de vida de Qwik, cuando usar el hook useTask$() y cuando no, aplicando a diferentes situaciones.

Y aunque es un artículo que pueda resultar no muy importante, os recomiendo que lo tengáis a mano, ya que estos conceptos los vais a usar prácticamente en todos los proyectos en los que trabajéis con esta tecnología.

Llevamos aprendido muchísimo, os animo a que repaséis si hiciese falta.

Me gustaría que en los comentarios dejaseis resultados de cosas que vayáis practicando, ya que cuanta más variedad, todo el mundo nos beneficiamos de ello.

Todos los artículos publicados del curso los encontraréis en la siguiente lista que iré actualizando a medida que vaya añadiendo nuevos contenidos con el orden natural recomendado:

Qwik paso a paso desde 0 al detalle

23 stories

Presencia en redes sociales

Podéis encontrarme en las siguientes redes.

--

--

Anartz Mugika Ledo🤗

[{#frontend:[#mobile:{#android, #kotlin, #ionic}}, {#web:{#angular, #qwik, #bootstrap}}],{#backend: [{#graphql, #nestjs,#express, #mongodb, #mysql}]}]