Lifecycle: React, Angular y Lit

Ciclo de vida de un componente: React, Angular y Lit

1. OBJETIVO

Comparar cómo se administra el ciclo de vida de un componente en React, Angular y Lit (LitElement), usando el mismo ejemplo funcional: un contador que aumenta cada segundo mientras el componente está montado o conectado y detiene el temporizador cuando deja de estarlo.

La idea es la misma en los tres casos:

  1. El componente aparece.
  2. Se inicia un temporizador.
  3. El estado del contador cambia cada segundo y la interfaz se actualiza.
  4. El componente desaparece.
  5. Se limpia el temporizador para evitar que siga ejecutándose.

Nota: React es una biblioteca basada en funciones y efectos; Angular ofrece hooks de ciclo de vida en clases; LitElement es una clase de componente web con propiedades reactivas y callbacks del ciclo de vida del elemento personalizado.

2. REACT

Ejemplo: ContadorReact.jsx

import { useEffect, useState } from "react";

export default function ContadorReact() {
  const [contador, setContador] = useState(0);

  useEffect(() => {
    // Se ejecuta después de que React confirma el renderizado.
    const intervalo = setInterval(() => {
      setContador(valorActual => valorActual + 1);
    }, 1000);

    // Limpieza: se ejecuta antes de repetir el efecto y al desmontar.
    return () => {
      clearInterval(intervalo);
    };
  }, []);

  return <div>Segundos transcurridos: {contador}</div>;
}

Cómo funciona el ciclo de vida en React

  • Renderizado inicial: React ejecuta ContadorReact y produce la interfaz con el contador en 0.
  • Montaje y efecto: después de confirmar el renderizado, useEffect inicia setInterval. El arreglo vacío [] indica que el efecto no depende de valores reactivos y no necesita repetirse por cambios de estado.
  • Actualización: cada segundo, setContador cambia el estado. React vuelve a ejecutar la función del componente y actualiza el texto de la interfaz.
  • Desmontaje: cuando el componente se elimina, React ejecuta la función de limpieza retornada por useEffect. clearInterval detiene el temporizador.

Importante: en entornos de desarrollo con React Strict Mode, React puede ejecutar un ciclo adicional de configuración y limpieza para detectar efectos incorrectos. Por eso la limpieza debe estar bien implementada.

3. ANGULAR

Ejemplo: contador.component.ts

import { Component, OnDestroy, OnInit } from "@angular/core";

@Component({
  selector: "app-contador",
  standalone: true,
  template: `<div>Segundos transcurridos: {{ contador }}</div>`
})
export class ContadorComponent implements OnInit, OnDestroy {
  contador = 0;
  private intervalo?: ReturnType<typeof setInterval>;

  ngOnInit(): void {
    // Se ejecuta una vez después de inicializar el componente.
    this.intervalo = setInterval(() => {
      this.contador++;
    }, 1000);
  }

  ngOnDestroy(): void {
    // Se ejecuta cuando Angular destruye el componente.
    if (this.intervalo !== undefined) {
      clearInterval(this.intervalo);
    }
  }
}

Cómo funciona el ciclo de vida en Angular

  • Creación e inicialización: Angular crea la instancia de ContadorComponent e inicializa la propiedad contador en 0.
  • Inicialización del componente: ngOnInit se ejecuta una vez después de que Angular inicializa las propiedades de entrada del componente. Allí se inicia el temporizador.
  • Actualización: cada segundo, el callback incrementa contador. Angular detecta el cambio y actualiza la interpolación {{ contador }} en la plantilla.
  • Destrucción: cuando el componente se retira de la vista, Angular llama a ngOnDestroy. Allí se limpia el intervalo.

Nota: Angular también dispone de otros hooks, como ngOnChanges para cambios en propiedades de entrada y ngAfterViewInit para después de inicializar la vista. No son necesarios para este ejemplo.

4. LIT (LITELEMENT)

Ejemplo: contador-lit.js

import { LitElement, html } from "lit";

class ContadorLit extends LitElement {
  static properties = {
    contador: { type: Number }
  };

  constructor() {
    super();
    this.contador = 0;
    this.intervalo = undefined;
  }

  connectedCallback() {
    super.connectedCallback();
    // Se ejecuta cuando el elemento se conecta al DOM.
    this.intervalo = setInterval(() => {
      this.contador++;
    }, 1000);
  }

  disconnectedCallback() {
    // Se ejecuta cuando el elemento se desconecta del DOM.
    if (this.intervalo !== undefined) {
      clearInterval(this.intervalo);
      this.intervalo = undefined;
    }
    super.disconnectedCallback();
  }

  render() {
    return html`<div>Segundos transcurridos: ${this.contador}</div>`;
  }
}

customElements.define("contador-lit", ContadorLit);

Cómo funciona el ciclo de vida en LitElement

  • Construcción: el constructor crea la instancia e inicializa contador en 0.
  • Conexión: connectedCallback se ejecuta cuando el elemento se conecta al DOM. Se llama a super.connectedCallback() y se inicia el temporizador.
  • Actualización reactiva: contador está declarada como propiedad reactiva. Cuando cambia, Lit programa una actualización y vuelve a ejecutar render() para actualizar el contenido.
  • Desconexión: disconnectedCallback se ejecuta cuando el elemento se desconecta del DOM. Se limpia el temporizador y se llama al método de la clase base.

Nota: un elemento personalizado puede desconectarse y volver a conectarse. Por eso connectedCallback puede ejecutarse más de una vez durante su vida. En aplicaciones que permitan reconexiones repetidas, conviene proteger la creación del intervalo para no iniciar varios temporizadores simultáneos. En este ejemplo, el intervalo se limpia al desconectar; al reconectar se crea uno nuevo.

5. COMPARACIÓN DIRECTA

EtapaReactAngularLitElement
InicializaciónFunción del componenteConstructorconstructor()
Inicio del trabajouseEffectngOnInitconnectedCallback()
Cambio de datossetState / useStateCambiar propiedadCambiar propiedad reactiva
Actualización visualNuevo renderDetección de cambiosrender() reactivo
Limpieza al salirLimpieza de useEffectngOnDestroydisconnectedCallback()

La comparación es conceptual: los hooks no son equivalentes exactos en todos sus detalles. En React, useEffect sincroniza el componente con un efecto externo; en Angular, ngOnInit y ngOnDestroy son hooks explícitos; en Lit, connectedCallback y disconnectedCallback son callbacks del ciclo de vida del elemento personalizado.

6. CONCLUSIÓN

Los tres componentes implementan el mismo comportamiento, pero expresan el ciclo de vida de manera distinta:

  • React: useEffect inicia el temporizador y la función de limpieza lo detiene.
  • Angular: ngOnInit inicia el temporizador y ngOnDestroy lo detiene.
  • LitElement: connectedCallback inicia el temporizador y disconnectedCallback lo detiene.

La regla importante en los tres frameworks es la misma: todo recurso externo que se inicia, por ejemplo, un temporizador, una suscripción o un listener, debe liberarse cuando deja de ser necesario. Esto evita fugas de recursos y comportamientos duplicados.

© Arturo Juárez Monroy