Skip to content

Lifecycle e Cleanup

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.

  1. Criação: Componente executa uma vez, retornando um nó DOM
  2. Reatividade: Estados reativos atualizam o DOM automaticamente via subscriptions
  3. 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
}

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))
}
}

O cleanup é executado automaticamente quando:

  1. Substituição de conteúdo reativo: Quando um estado reativo muda e o conteúdo antigo é substituído
  2. Remoção manual: Quando você remove um elemento do DOM com removeChild() ou similar
  3. Navegação de rotas: Quando o router desmonta a view anterior

Embora o Slash gerencie automaticamente subscriptions de estados reativos, você pode precisar de cleanup manual para recursos externos.

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
}

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
}

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>`
}

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
}

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
}
  1. Sempre limpe subscriptions manuais: Se você usar .watch() manualmente, registre o cleanup
  2. Limpe timers e intervals: Use addCleanup para clearInterval/clearTimeout
  3. Remova event listeners externos: Listeners no window, document, ou elementos fora do componente
  4. Feche conexões: WebSockets, EventSource, streams, etc.
  5. 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
}
FrameworkLifecycle HooksCleanup
Slash❌ Nenhum✅ Automático + addCleanup()
ReactuseEffect✅ Return function
VueonMounted, onUnmountedonUnmounted()
SolidonMount, onCleanuponCleanup()
  • Roteamento - Lifecycle com navegação de rotas
  • Error Handling - Tratamento de erros em componentes
  • SSR - Lifecycle no server-side rendering