Skip to content

Ciclo di Vita delle Estensioni โ€‹

Il ciclo di vita delle estensioni KIMU segue un pattern prevedibile che ti permette di gestire correttamente inizializzazione, rendering e cleanup.

๐Ÿ”„ Hook del Ciclo di Vita โ€‹

1. onInit() - Inizializzazione โ€‹

2. onRender() - Post-Rendering โ€‹

3. onDestroy() - Cleanup e Pulizia โ€‹


๐Ÿš€ 1. onInit() - Setup Iniziale โ€‹

Eseguito UNA VOLTA quando l'estensione viene caricata la prima volta.

typescript
@KimuComponent({
  tag: 'lifecycle-demo',
  name: 'Demo Ciclo di Vita'
})
export class LifecycleDemo extends KimuComponentElement {
  private intervalId?: number;
  private eventListener?: (e: Event) => void;
  private data: any[] = [];

  onInit(): void {
    console.log('๐Ÿš€ 1. onInit - Inizializzazione');
    
    // โœ… Setup iniziale
    this.setupEventListeners();
    this.loadInitialData();
    this.startPeriodicUpdates();
  }

  private setupEventListeners() {
    this.eventListener = (e: Event) => {
      console.log('Evento ricevuto:', e);
      this.refresh();
    };
    
    document.addEventListener('kimu:data-update', this.eventListener);
  }

  private async loadInitialData() {
    try {
      const response = await fetch('/api/initial-data');
      this.data = await response.json();
      this.refresh(); // Aggiorna UI con nuovi dati
    } catch (error) {
      console.error('Errore caricamento dati:', error);
    }
  }

  private startPeriodicUpdates() {
    this.intervalId = window.setInterval(() => {
      this.updateTimestamp();
    }, 1000);
  }

  private updateTimestamp() {
    // Aggiorna timestamp interno
    this.refresh();
  }

  getData() {
    return {
      data: this.data,
      timestamp: new Date().toLocaleTimeString(),
      status: 'Attivo'
    };
  }
}

Cosa Fare in onInit() โ€‹

โœ… FareโŒ Non Fare
Setup event listenersManipolazione DOM diretta
Caricamento dati inizialiOperazioni che dipendono dal DOM
Inizializzazione timerOperazioni pesanti sincrone
Configurazione statoAccesso a elementi non ancora renderizzati

๐ŸŽจ 2. onRender() - Post-Rendering โ€‹

Eseguito OGNI VOLTA dopo che il template รจ stato renderizzato.

typescript
export class RenderingExample extends KimuComponentElement {
  private chartInstance?: any;

  onRender(): void {
    console.log('๐ŸŽจ 2. onRender - Post-rendering');
    
    // โœ… Manipolazione DOM sicura
    this.attachDOMHandlers();
    this.initializeChart();
    this.updateDOMClasses();
  }

  private attachDOMHandlers() {
    // Accesso sicuro agli elementi DOM
    const button = this.querySelector('.special-button');
    if (button) {
      button.addEventListener('click', this.handleSpecialClick);
    }
  }

  private initializeChart() {
    const chartContainer = this.querySelector('.chart-container');
    if (chartContainer && !this.chartInstance) {
      // Inizializza libreria chart solo se il container esiste
      this.chartInstance = new Chart(chartContainer, {
        // Configurazione chart
      });
    }
  }

  private updateDOMClasses() {
    // Aggiorna classi CSS basate sullo stato
    const container = this.querySelector('.container');
    if (container) {
      container.classList.toggle('active', this.isActive);
      container.classList.toggle('loading', this.isLoading);
    }
  }

  private handleSpecialClick = (e: Event) => {
    console.log('Click speciale:', e);
  }

  getData() {
    return {
      isActive: true,
      isLoading: false,
      chartData: [1, 2, 3, 4, 5]
    };
  }
}

Cosa Fare in onRender() โ€‹

โœ… FareโŒ Non Fare
Manipolazione DOMSetup event listeners globali
Inizializzazione librerie UIOperazioni asincrone pesanti
Aggiornamento classi CSSChiamate API
Binding eventi specificiModifica stato che causa re-render infinito

๐Ÿงน 3. onDestroy() - Cleanup โ€‹

Eseguito quando l'estensione viene rimossa dal layout.

typescript
export class CleanupExample extends KimuComponentElement {
  private intervalId?: number;
  private timeoutId?: number;
  private eventListener?: (e: Event) => void;
  private observers: IntersectionObserver[] = [];
  private abortController = new AbortController();

  onInit(): void {
    // Setup con tracking per cleanup
    this.setupPeriodicUpdates();
    this.setupEventListeners();
    this.setupObservers();
    this.setupAsyncOperations();
  }

  onDestroy(): void {
    console.log('๐Ÿงน 3. onDestroy - Cleanup');
    
    // โœ… Cleanup completo di tutte le risorse
    this.clearTimers();
    this.removeEventListeners();
    this.disconnectObservers();
    this.cancelAsyncOperations();
  }

  private setupPeriodicUpdates() {
    this.intervalId = window.setInterval(() => {
      this.updateData();
    }, 1000);

    this.timeoutId = window.setTimeout(() => {
      this.doDelayedAction();
    }, 5000);
  }

  private setupEventListeners() {
    this.eventListener = (e: Event) => {
      this.handleGlobalEvent(e);
    };
    
    document.addEventListener('resize', this.eventListener);
    window.addEventListener('beforeunload', this.eventListener);
  }

  private setupObservers() {
    const observer = new IntersectionObserver((entries) => {
      console.log('Intersection:', entries);
    });
    
    this.observers.push(observer);
    
    const element = this.querySelector('.observed-element');
    if (element) {
      observer.observe(element);
    }
  }

  private setupAsyncOperations() {
    // Operazioni async con AbortController
    fetch('/api/data', { 
      signal: this.abortController.signal 
    }).then(response => {
      // Handle response
    }).catch(error => {
      if (error.name !== 'AbortError') {
        console.error('Fetch error:', error);
      }
    });
  }

  // ๐Ÿงน Metodi di Cleanup

  private clearTimers() {
    if (this.intervalId) {
      clearInterval(this.intervalId);
      this.intervalId = undefined;
    }
    
    if (this.timeoutId) {
      clearTimeout(this.timeoutId);
      this.timeoutId = undefined;
    }
  }

  private removeEventListeners() {
    if (this.eventListener) {
      document.removeEventListener('resize', this.eventListener);
      window.removeEventListener('beforeunload', this.eventListener);
      this.eventListener = undefined;
    }
  }

  private disconnectObservers() {
    this.observers.forEach(observer => observer.disconnect());
    this.observers = [];
  }

  private cancelAsyncOperations() {
    this.abortController.abort();
  }

  getData() {
    return {
      status: 'Attivo',
      activeTimers: [this.intervalId, this.timeoutId].filter(Boolean).length,
      activeObservers: this.observers.length
    };
  }
}

Cosa Fare in onDestroy() โ€‹

โœ… Sempre Fareโš ๏ธ Attenzione
Clear timer/intervalNon chiamare refresh()
Remove event listenersNon iniziare nuove operazioni async
Disconnect observersNon accedere al DOM
Cancel async operationsNon logiche complesse
Reset riferimentiOperazioni devono essere sincrone

๐ŸŽฏ Pattern Avanzati โ€‹

1. Lifecycle con State Management โ€‹

typescript
export class StateLifecycleExample extends KimuComponentElement {
  private state = {
    initialized: false,
    rendered: false,
    disposed: false,
    data: null
  };

  onInit(): void {
    this.updateState({ initialized: true });
    console.log('State dopo init:', this.state);
  }

  onRender(): void {
    this.updateState({ rendered: true });
    console.log('State dopo render:', this.state);
  }

  onDestroy(): void {
    this.updateState({ disposed: true });
    console.log('State finale:', this.state);
  }

  private updateState(updates: Partial<typeof this.state>) {
    this.state = { ...this.state, ...updates };
    if (!this.state.disposed) {
      this.refresh(); // Solo se non siamo in fase di dispose
    }
  }

  getData() {
    return {
      ...this.state,
      lifecycle: `Init: ${this.state.initialized}, Render: ${this.state.rendered}`
    };
  }
}

2. Cleanup Automatico con Utility โ€‹

typescript
class ResourceManager {
  private timers: number[] = [];
  private listeners: Array<{target: EventTarget, event: string, handler: EventListener}> = [];
  private observers: IntersectionObserver[] = [];

  addTimer(id: number) {
    this.timers.push(id);
  }

  addListener(target: EventTarget, event: string, handler: EventListener) {
    this.listeners.push({ target, event, handler });
    target.addEventListener(event, handler);
  }

  addObserver(observer: IntersectionObserver) {
    this.observers.push(observer);
  }

  cleanup() {
    // Clear timers
    this.timers.forEach(id => {
      clearInterval(id);
      clearTimeout(id);
    });

    // Remove listeners
    this.listeners.forEach(({ target, event, handler }) => {
      target.removeEventListener(event, handler);
    });

    // Disconnect observers
    this.observers.forEach(observer => observer.disconnect());

    // Reset arrays
    this.timers = [];
    this.listeners = [];
    this.observers = [];
  }
}

export class ManagedResourcesExample extends KimuComponentElement {
  private resourceManager = new ResourceManager();

  onInit(): void {
    // Setup con resource manager
    const intervalId = window.setInterval(() => {
      this.updateData();
    }, 1000);
    this.resourceManager.addTimer(intervalId);

    this.resourceManager.addListener(document, 'click', this.handleDocumentClick);
  }

  onDestroy(): void {
    // Cleanup automatico di tutte le risorse
    this.resourceManager.cleanup();
  }

  private handleDocumentClick = (e: Event) => {
    console.log('Document clicked');
  }

  getData() {
    return {
      timestamp: new Date().toLocaleTimeString()
    };
  }
}

๐Ÿ› Debug del Ciclo di Vita โ€‹

Tracking Hook Calls โ€‹

typescript
export class DebugLifecycle extends KimuComponentElement {
  private lifecycleLog: string[] = [];

  onInit(): void {
    this.addToLog('onInit chiamato');
    console.log('๐Ÿ” Debug Lifecycle - Init');
  }

  onRender(): void {
    this.addToLog('onRender chiamato');
    console.log('๐Ÿ” Debug Lifecycle - Render');
  }

  onDestroy(): void {
    this.addToLog('onDestroy chiamato');
    console.log('๐Ÿ” Debug Lifecycle - Dispose');
    console.log('๐Ÿ“Š Lifecycle completo:', this.lifecycleLog);
  }

  private addToLog(message: string) {
    const timestamp = new Date().toISOString();
    this.lifecycleLog.push(`${timestamp}: ${message}`);
  }

  getData() {
    return {
      lifecycleLog: this.lifecycleLog,
      renderCount: this.lifecycleLog.filter(log => log.includes('onRender')).length
    };
  }
}

๐Ÿšจ Errori Comuni da Evitare โ€‹

โŒ Non Fare - Memory Leaks โ€‹

typescript
// โŒ SBAGLIATO: Timer non pulito
onInit() {
  setInterval(() => {
    this.refresh();
  }, 1000); // Memory leak!
}

// โŒ SBAGLIATO: Listener non rimosso
onInit() {
  document.addEventListener('click', this.handleClick); // Memory leak!
}

โœ… Fare - Gestione Corretta โ€‹

typescript
// โœ… CORRETTO: Timer con cleanup
private timerId?: number;

onInit() {
  this.timerId = window.setInterval(() => {
    this.refresh();
  }, 1000);
}

onDestroy() {
  if (this.timerId) {
    clearInterval(this.timerId);
  }
}

๐Ÿ“š Prossimi Passi โ€‹

Released under Creative Commons Attribution 4.0 International (CC BY 4.0)