Guia em atualização...

Ciclo de vida

Veremos nessa página um importante assunto sobre os componentes svelte: Ciclo de vida.

Veja o que diz a documentação:

Cada componente tem um ciclo de vida que começa quando é criado e termina quando é destruído. Há um punhado de funções que permitem executar o código em momentos-chave durante esse ciclo de vida. Documentação

Vamos começar pelo mais utilizado, o onMount.

On Mount

Essa função é executada depois que o componente é renderizado pela primeira vez. Sua sintaxe é onMount(callback).

<script>
	import { onMount } from 'svelte';

	onMount(() => {
		console.log('Componente montado.');
	});
</script>

Podemos retornar uma função de onMount que será executada quando o componente for destruído. Isso é útil, por exemplo, quando trabalhamos com setTimeout e setInterval. Se não removermos o timer criado quando o componente for destruído, isso poderá causar vazamento de memória. Leia mais.

Observe abaixo:

gif

No exemplo acima eu demonstrei um problema que acontece muito quando utilizamos setInterval. Quando o componente foi desmontado sem utilizar clearInterval, houve um acúmulo de timers resultando em um console.log acelerado. Usando clearInterval isso não acontece.

Podemos fazer chamadas assíncronas utilizando o onMount.

<script>
	import { onMount } from 'svelte';
	let data = {};

	onMount(async () => {
		const response = await fetch('https://random-data-api.com/api/v2/users');
		const json = await response.json();

		data = json;
	})
</script>

{#if data}
	<h1>{data.first_name}</h1>
	<img src={data.avatar} alt="" />
{:else}
	<p>Carregando...</p>
{/if}

<style>
	img {
		width: 100px;
		max-width: 100%
	}
</style>

Mas veja que toda função assíncrona no javascript retorna uma promise. Então, nesse caso, não podemos utilizar o retorno da função onMount.

Isso aqui não vai funcionar!

<script>
	import { onMount } from 'svelte';

	onMount(async () => {
		setInterval(() => {
			console.log('beep')
	}, 1000)

		// Esse console.log nunca será executado.
		// Dessa forma, qualquer coisa executada aqui não vai funcionar.
		return () => console.log('Componente desmontado')
	});
</script>
gif

Fique atento!

On Destroy

Embora não seja possível executar uma função quando o componente for desmontado em um onMount assíncrono, podemos fazer isso com uma função de ciclo de vida específico. O onDestroy.

Voltando ao exemplo anterior, vamos corrigir o problema.

<script>
	import { onMount, onDestroy } from 'svelte';

	// Declaro a variável fora do "onMount" pra pegar a referência do "setInterval"
	// e utilizar no "clearInterval".
	let interval;

	onMount(async () => {
		interval = setInterval(() => {
			console.log('beep')
	}, 1000)

		// Embora isso não funcione...
		// return () => console.log('Componente desmontado')
	});

	// Isso aqui sim.
	onDestroy(() => {
		clearInterval(interval)
		console.log('Componente desmontado')
	})
</script>

Problema resolvido!

Você pode executar uma função quando o componente for desmontado de 2 maneiras.

  1. Com o retorno de onMount, caso não seja assíncrono.
  2. Com a função onDestroy.

Before Update

A função beforeUpdate é executada imediatamente antes do componente ser atualizado e, na primeira execução, será antes de onMount.

<script>
	import { onMount, beforeUpdate } from 'svelte';

	onMount(() => console.log('Segundo'))
	beforeUpdate(() => console.log('Primeiro'))
</script>

O resultado desse script será:

  • “Primeiro”
  • “Segundo”

Vamos ver um outro exemplo.

<script>
	import { onMount, beforeUpdate } from 'svelte';
	let count = 0;

	onMount(() => console.log('On Mount', count))

	beforeUpdate(() => {
		console.log('Before update', count)
	})

	function handleClick() {
		count += 10;
	}
</script>

<button on:click={handleClick}>Add + 10</button>
<h2>{count}</h2>
gif

No exemplo acima demonstro que o beforeUpdate é acionado antes de onMount e também antes de cada atualização de estado. Mas e se eu atualizar o estado dentro de beforeUpdate, o que acontecerá?

Bom, vamos pensar um pouco! Siga o raciocínio.

  1. O componente vai ser montado na tela
  2. beforeUpdate é executado pela primera vez antes de onMount
  3. count é atualizado
  4. beforeUpdate é executado novamente
  5. count é atualizado
  6. beforeUpdate é executado novamente
  7. loop infinito

Isso aconte pois toda vez que um estado é colocado para atualizar, beforeUpdate agenda uma função para ser executada antes. Mas como a atualização de estado está dentro dessa função, entra em loop infinito.

Ou melhor, entraria… Mas o svelte é inteligente nesse ponto. Para evitar um loop infinito, ele executa a função de beforeUpdate apenas mais um vez.

gif

No entanto, esse cuidado somos nós que temos que ter. Somos programadores e a máquina executa as instruções que escrevemos.

Se você fizer isso:

<script>
	while (true) {
		console.log(100000 ** 400000);
	}
</script>

Não adianta reclamar se sua máquina travar.

After Update

Ao contrário de beforeUpdate, o afterUpdate é executado na primeira vez após onMount e também após cada atualização de estado.

<script>
	import { afterUpdate } from 'svelte';

	afterUpdate(() => {
		console.log('Componente atualizado');
	});
</script>

As observações a respeito do beforeUpdate são as mesmas para o afterUpdate.

Tick

No svelte as alterações no DOM não são feitas imediatamente. Quando há alguma alteração de estado, ele aguarda pra ver se há mais alterações a serem feitas, inclusive em outros componentes. Por esse motivo pode acontecer algum resultado inesperado caso você nã isso.

Veja o exemplo abaixo:

<script>
	let count = 1;
	$: double = count * 2;

	function handleClick() {
		count++
		// Aqui "double" ainda não foi atualizado e mostrará o valor antigo.
		console.log(double)
	}
</script>

<button on:click={handleClick}>+</button>
<h1>{count} - {double}</h1>

Um outro exemplo é quando tentamos acessar as propriedades de algum elemento do DOM logo após uma alteração de estado desse elemento.

<script>
	import { tick } from 'svelte';
	let text = '';
	let h2;

	async function handleInput(e) {
		text = e.target.value;

		// O texto de h2 ainda não foi atualizado. "console.log" exibirá o valor antigo
		// Descomente a linha abaixo.

		// await tick()

		// Agora o "console.log" vai exibir o valor correto
		console.log(h2.innerText)
	}
</script>

<label for="classe">Nome da classe</label>
<input id="classe" type="text" on:input={handleInput} />
<h2 bind:this={h2}>{text}</h2>
gif

Veja o que diz a documentação.

tick retorna uma promessa que resolve assim que quaisquer alterações de estado pendentes forem aplicadas ao DOM (ou imediatamente, se não houver alterações de estado pendentes). Documentação

Por retornar uma promise, devemos colocar um async na declaração da função:

<script>
	async function handleInput(e) {
		...
		await tick()
	}
</script>

ou fazer assim:

<script>
	 function handleInput(e) {
		...
		tick().then(() => {})

	}
</script>