Lifecycle e Cleanup
Lifecycle no Slash
Section titled “Lifecycle no Slash”Diferente de frameworks como React ou Vue, o Slash não possui lifecycle hooks explícitos como useEffect, onMounted, ou beforeDestroy. Em vez disso, o lifecycle é gerenciado de forma automática e transparente através do sistema de cleanup baseado em WeakMap.
Como funciona
Section titled “Como funciona”- Criação: Componente executa uma vez, retornando um nó DOM
- Reatividade: Estados reativos atualizam o DOM automaticamente via subscriptions
- Cleanup: Quando um nó é removido do DOM, subscriptions são canceladas automaticamente
import { html, createState } from '@ezbug/slash'
function Counter() { const count = createState(0)
// ✅ Subscription criada automaticamente quando count é usado no template return html` <div> <p>Contador: ${count}</p> <button onClick=${() => count.set(count.get() + 1)}>+1</button> </div> ` // Quando este elemento é removido do DOM, a subscription é limpa automaticamente}Sistema de Cleanup automático
Section titled “Sistema de Cleanup automático”O Slash mantém um WeakMap interno que rastreia cleanups associados a cada nó DOM:
// Internamente no Slash (src/lifecycle/cleanup.ts)const CLEANUPS = new WeakMap<Node, Array<() => void>>()
export function addCleanup(node: Node, fn: () => void): void { const arr = CLEANUPS.get(node) if (arr) arr.push(fn) else CLEANUPS.set(node, [fn])}
export function destroyNode(node: Node): void { const fns = CLEANUPS.get(node) if (fns) { for (const f of fns) { try { f() } catch {} } CLEANUPS.delete(node) } // Recursivamente destrói children if (node instanceof Element && node.hasChildNodes()) { node.childNodes.forEach((child) => destroyNode(child)) }}Quando o cleanup é executado?
Section titled “Quando o cleanup é executado?”O cleanup é executado automaticamente quando:
- Substituição de conteúdo reativo: Quando um estado reativo muda e o conteúdo antigo é substituído
- Remoção manual: Quando você remove um elemento do DOM com
removeChild()ou similar - Navegação de rotas: Quando o router desmonta a view anterior
Cleanup manual
Section titled “Cleanup manual”Embora o Slash gerencie automaticamente subscriptions de estados reativos, você pode precisar de cleanup manual para recursos externos.
Usando addCleanup
Section titled “Usando addCleanup”import { html, addCleanup } from '@ezbug/slash'
function WebSocketComponent({ url }: { url: string }) { const container = html`<div class="ws-status">Conectando...</div>` as HTMLElement
// Criar WebSocket const ws = new WebSocket(url)
ws.onopen = () => { container.textContent = 'Conectado!' }
ws.onmessage = (event) => { container.textContent = `Mensagem: ${event.data}` }
ws.onerror = () => { container.textContent = 'Erro de conexão' }
// Registrar cleanup para fechar o WebSocket addCleanup(container, () => { console.log('Fechando WebSocket...') ws.close() })
return container}Múltiplos cleanups
Section titled “Múltiplos cleanups”Você pode registrar múltiplos cleanups para o mesmo nó:
import { html, addCleanup } from '@ezbug/slash'
function TimerComponent() { const element = html`<div class="timer">0s</div>` as HTMLElement let seconds = 0
// Timer 1: atualiza a cada segundo const intervalId = setInterval(() => { seconds++ element.textContent = `${seconds}s` }, 1000)
// Timer 2: log a cada 5 segundos const logIntervalId = setInterval(() => { console.log(`Timer ativo há ${seconds} segundos`) }, 5000)
// Registrar cleanup para ambos os timers addCleanup(element, () => { clearInterval(intervalId) console.log('Timer principal limpo') })
addCleanup(element, () => { clearInterval(logIntervalId) console.log('Timer de log limpo') })
return element}Patterns de lifecycle
Section titled “Patterns de lifecycle”Executar código na “montagem”
Section titled “Executar código na “montagem””Como componentes são funções que executam imediatamente, não há necessidade de um hook onMounted:
function UserProfile({ userId }: { userId: number }) { console.log('Componente criado') // Executa imediatamente
// Fazer fetch na criação fetchUser(userId).then(user => { // Atualizar UI... })
return html`<div>Carregando usuário ${userId}...</div>`}Reagir a mudanças de props
Section titled “Reagir a mudanças de props”Para reagir a mudanças em props reativas, use .watch():
import { html, createState } from '@ezbug/slash'import type { State } from '@ezbug/slash'
function UserDisplay({ userIdState }: { userIdState: State<number> }) { const userData = createState<any>(null) const element = html`<div>Carregando...</div>` as HTMLElement
// Atualizar UI quando userData mudar const updateUI = (data: any) => { if (data) { element.innerHTML = ` <h3>${data.name}</h3> <p>${data.email}</p> ` } else { element.textContent = 'Carregando...' } }
userData.watch(updateUI)
// Reagir a mudanças no userId const unsub = userIdState.watch((newId) => { console.log('UserId mudou para:', newId) userData.set(null) // Reset
fetchUser(newId).then(user => { userData.set(user) }) })
// Fazer fetch inicial fetchUser(userIdState.get()).then(user => { userData.set(user) })
// Cleanup da subscription addCleanup(element, unsub) addCleanup(element, () => { console.log('UserDisplay desmontado') })
return element}Prevenir memory leaks em event listeners externos
Section titled “Prevenir memory leaks em event listeners externos”import { html, addCleanup } from '@ezbug/slash'
function ScrollTracker() { const position = createState(0) const element = html` <div class="scroll-tracker"> <p>Scroll: ${position}px</p> </div> ` as HTMLElement
// Event listener no window const handleScroll = () => { position.set(window.scrollY) }
window.addEventListener('scroll', handleScroll)
// Limpar event listener quando componente for destruído addCleanup(element, () => { window.removeEventListener('scroll', handleScroll) console.log('Event listener removido') })
return element}Exemplo completo: Gerenciamento de recursos
Section titled “Exemplo completo: Gerenciamento de recursos”import { html, createState, addCleanup } from '@ezbug/slash'
function DataStreamComponent({ apiUrl }: { apiUrl: string }) { const data = createState<string[]>([]) const status = createState<'connecting' | 'connected' | 'error'>('connecting')
const element = html` <div class="data-stream"> <div class="status"> Status: ${status} </div> <ul> ${data.get().map(item => html`<li>${item}</li>`)} </ul> </div> ` as HTMLElement
// Criar EventSource para Server-Sent Events const eventSource = new EventSource(apiUrl)
eventSource.onopen = () => { status.set('connected') }
eventSource.onmessage = (event) => { const current = data.get() data.set([...current, event.data]) }
eventSource.onerror = () => { status.set('error') }
// Timer para limpar dados antigos a cada minuto const cleanupTimer = setInterval(() => { const current = data.get() if (current.length > 100) { data.set(current.slice(-50)) // Manter apenas últimos 50 } }, 60000)
// Registrar todos os cleanups addCleanup(element, () => { console.log('Fechando EventSource...') eventSource.close() })
addCleanup(element, () => { console.log('Limpando timer...') clearInterval(cleanupTimer) })
addCleanup(element, () => { console.log('Componente DataStream destruído') })
return element}Verificando cleanups (debugging)
Section titled “Verificando cleanups (debugging)”O Slash exporta uma função hasCleanups para debugging:
import { html, addCleanup, hasCleanups } from '@ezbug/slash'
function DebugComponent() { const element = html`<div>Debug</div>` as HTMLElement
addCleanup(element, () => console.log('Limpando...'))
console.log('Tem cleanups?', hasCleanups(element)) // true
return element}Best practices
Section titled “Best practices”- Sempre limpe subscriptions manuais: Se você usar
.watch()manualmente, registre o cleanup - Limpe timers e intervals: Use
addCleanupparaclearInterval/clearTimeout - Remova event listeners externos: Listeners no
window,document, ou elementos fora do componente - Feche conexões: WebSockets, EventSource, streams, etc.
- Cancele requisições: Use AbortController para cancelar fetches pendentes
function FetchComponent({ url }: { url: string }) { const element = html`<div>Carregando...</div>` as HTMLElement const controller = new AbortController()
fetch(url, { signal: controller.signal }) .then(res => res.json()) .then(data => { element.textContent = JSON.stringify(data) }) .catch(err => { if (err.name !== 'AbortError') { element.textContent = 'Erro ao carregar' } })
addCleanup(element, () => { controller.abort() console.log('Fetch cancelado') })
return element}Comparação com outros frameworks
Section titled “Comparação com outros frameworks”| Framework | Lifecycle Hooks | Cleanup |
|---|---|---|
| Slash | ❌ Nenhum | ✅ Automático + addCleanup() |
| React | ✅ useEffect | ✅ Return function |
| Vue | ✅ onMounted, onUnmounted | ✅ onUnmounted() |
| Solid | ✅ onMount, onCleanup | ✅ onCleanup() |
Próximos passos
Section titled “Próximos passos”- Roteamento - Lifecycle com navegação de rotas
- Error Handling - Tratamento de erros em componentes
- SSR - Lifecycle no server-side rendering