Lifecycle e Cleanup
Lifecycle no Slash
Section titled “Lifecycle no Slash”O Slash não possui lifecycle hooks como useEffect, onMounted ou onCleanup. O ciclo de vida de um componente é simples:
- Montagem: ao ser montado como
<${Counter} />, o componente executa e retorna um nó DOM. Cadastate.get()chamado nessa execução registra o state como dependência do componente. - Re-render: quando um desses states muda, o componente executa de novo, os nós anteriores são destruídos e os novos os substituem.
- Remoção: quando os nós saem da tela por um re-render ou por
render()sobre o mesmo container, o Slash destrói os nós antigos (veja abaixo).
import { html, createState, render } from '@_bashell/slash'
// O state fica fora do componente: se fosse criado dentro, seria recriado a cada renderconst count = createState(0)
function Counter() { // Ler count.get() durante a execução inscreve o componente no state return html` <div> <p>Contador: ${count.get()}</p> <button onClick=${() => count.set(count.get() + 1)}>+1</button> </div> `}
// Monte como <${Counter} />. Chamar Counter() diretamente não é reativorender(html`<${Counter} />`, '#app')O rastreamento é refeito a cada execução
Section titled “O rastreamento é refeito a cada execução”A cada execução o componente refaz a lista de states que lê: assina os recém-lidos e deixa de observar os que não leu mais. Um state lido só depois de um retorno antecipado passa a ser observado assim que esse ramo executa:
const Profile = () => { if (loading.get()) return html`<p>Carregando...</p>` // 1ª execução: observa só `loading` return html`<p>${data.get()}</p>` // depois de loading=false: também observa `data`}A exceção é um componente cuja primeira execução não leu nenhum state: ele é estático (devolvido como nó simples) e nunca re-executa, mesmo que depois fosse ler states.
O que o Slash limpa sozinho
Section titled “O que o Slash limpa sozinho”Internamente o Slash guarda funções de limpeza por nó DOM (num WeakMap) e as executa quando o nó é destruído. Isso cobre o que o próprio Slash cria:
- listeners de eventos registrados por props como
onClick; - a inscrição de um componente nos states que ele leu;
- inscrições de filhos e props reativos (
Router, controles de formulário).
Os nós são destruídos quando:
- um componente re-renderiza e substitui seus nós antigos;
render(view, container)é chamado sobre um container que já tem conteúdo;- o
Routertroca a rota exibida.
destroyNode()
Section titled “destroyNode()”destroyNode(node) é a única peça desse mecanismo que é pública (exportada por @_bashell/slash e @_bashell/slash/core). Ela executa as limpezas registradas pelo Slash no nó e em todos os descendentes. Não remove o nó do DOM.
import { html, render, destroyNode } from '@_bashell/slash'
const node = html`<button onClick=${() => console.log('oi')}>Oi</button>` as HTMLElementdocument.body.appendChild(node)
// Remover manualmente: destrua primeiro (remove os listeners), depois tire do DOMdestroyNode(node)node.remove()Use destroyNode quando você remover à mão, com removeChild()/remove(), nós que o Slash criou.
O que não está disponível
Section titled “O que não está disponível”Na prática isso significa que recursos externos (timers, WebSocket, EventSource, listeners em window) ficam sob sua responsabilidade.
Gerenciando recursos externos
Section titled “Gerenciando recursos externos”Mantenha o recurso fora do componente e controle início e fim explicitamente.
watch() devolve a função de cancelamento
Section titled “watch() devolve a função de cancelamento”const count = createState(0)
const unwatch = count.watch((value) => { console.log('count mudou para', value)})
// quando não precisar maisunwatch()Crie o watch() uma vez (no módulo ou em um handler), nunca no corpo de um componente, que executa várias vezes e acumularia watchers.
Timers e conexões
Section titled “Timers e conexões”import { html, createState, render } from '@_bashell/slash'
const seconds = createState(0)let timer: ReturnType<typeof setInterval> | null = null
const start = () => { if (timer) return timer = setInterval(() => seconds.set(seconds.get() + 1), 1000)}
const stop = () => { if (timer) clearInterval(timer) timer = null}
const Clock = () => html` <div> <p>${seconds.get()}s</p> <button onClick=${start}>Iniciar</button> <button onClick=${stop}>Parar</button> </div>`
render(html`<${Clock} />`, '#app')O mesmo vale para fetch com AbortController, WebSocket e listeners em window: crie e encerre em funções que você controla (handlers, ou o código que chama render()), não dentro do corpo do componente.
Reagindo à mudança de um state
Section titled “Reagindo à mudança de um state”Para executar um efeito quando um state muda, use watch() fora do componente:
import { createState } from '@_bashell/slash'
type User = { id: number; name: string }
const userId = createState<number | null>(null)const userData = createState<User | null>(null)
userId.watch(async (id) => { if (id === null) return const user = await fetch(`/api/users/${id}`).then((r) => r.json()) userData.set(user)})Comparação
Section titled “Comparação”| Framework | Hooks de lifecycle | Cleanup |
|---|---|---|
| Slash | Nenhum | Automático para o que o Slash cria; destroyNode() para remoção manual; recursos externos por sua conta |
| React | useEffect | Função retornada |
| Vue | onMounted, onUnmounted | onUnmounted() |
| Solid | onMount, onCleanup | onCleanup() |
Próximos passos
Section titled “Próximos passos”- Roteamento - Navegação de rotas
- Error Handling - Tratamento de erros em componentes
- SSR - Renderização no servidor