VueJS: ciclo de vida dos componentes
Se você iniciou recentemente o aprendizado de algum framework ou biblioteca Front-End, já deve ter se deparado com o termo "ciclo de vida" de um componente e já deve ter se perguntado o que exatamente significa isso.
Nesse artigo, vamos entender os principais métodos de ciclo de vida que o Vue.js nos oferece e suas implementações com Options API e Composition API!
O que é ciclo de vida?
No Vue, cada componente passa por várias etapas: basicamente, ele é criado, observa dados, monta os seus elementos no DOM e o atualiza quando algum dado é modificado até, eventualmente, ser desmontado. Ao longo desse processo, o componente usa várias funções chamadas de métodos de ciclo de vida ou lifecycle hooks.
Os lifecycle hooks são métodos predefinidos que são executados em momentos específicos durante a vida útil de um componente, ou seja, o período de tempo durante o qual um componente existe, está ativo e desempenha suas funções em um aplicativo ou sistema de software.
Esses métodos também podem ser usados por nós, pessoas desenvolvedoras, e permitem que possamos controlar o comportamento do componente, realizar operações em momentos específicos e interagir com o DOM ou com outros componentes.
Lifecycle hooks com Options API
Com a Options API temos acesso a oito métodos principais de ciclos de vida (que chamaremos de hooks a partir daqui): beforeCreate
, created
, beforeMount
, mounted
, beforeUpdate
, updated
, beforeUnmount
e unmounted
.
O momento em que cada hook é executado na instância do componente é demonstrado pelo diagrama abaixo, extraído da documentação oficial do Vue.js:
Para facilitar o estudo desse artigo, vamos dividí-los em grupos: hooks de criação, montagem, atualização e desmontagem.
Hooks de criação
Os hooks de criação são os primeiros hooks a rodar em um componente. Eles permitem que você execute ações antes que o seu componente seja adicionado ao DOM e também são os únicos que funcionam com server-side rendering.
Aqui conheceremos os hooks beforeCreate e created.
beforeCreate
O beforeCreate
é executado imediatamente assim que um componente é inicializado. Nesse momento, estados e propriedades computadas ainda não foram processadas.
<script>
export default {
beforeCreate() {
alert('beforeCreate foi chamado')
}
}
</script>
No código acima, o alert()
é executado antes da renderização do componente.
Esse hook é bastante útil, por exemplo, para realizar uma solicitação de autenticação. Dependendo do resultado da solicitação, o estado global (Vuex ou Pinia) é configurado adequadamente, permitindo que você lide com a autenticação antes que qualquer conteúdo seja exibido na tela.
created
O created
também é executado antes do componente ser montado no DOM, porém, nessa etapa todos os estados, propriedades computadas, métodos e watchers já foram processados e estão prontos para serem acessados.
<script>
export default {
data() {
return { message: 'BEM VINDO À ALURA' }
},
created() {
alert(this.message)
}
}
</script>
No código acima, o alert()
é executado antes da renderização do componente, mas já temos acesso à propriedade message
processada em nosso data()
.
Esse é um hook muito útil para fazer uma requisição HTTP para uma API e atualizar o estado do componente com esses dados, que serão renderizados no DOM.
Hooks de montagem
Os hooks de montagem permitem acessar o componente imediatamente antes ou depois da inserção do componente no DOM (primeira renderização), normalmente para acessar ou modificar elementos.
Aqui conheceremos os hooks beforeMount e mounted.
beforeMount
O beforeMount
é muito semelhante ao created
, sendo a principal diferença o momento em que ambos são executados:
created
é acionado após a criação do componente e permite acesso às opções de dados, mas antes que o DOM seja montado;beforeMount
é acionado imediatamente antes da montagem do DOM, logo depois de uma pré-compilação dotemplate
e dostyle
do componente.
<script>
export default {
data() {
return { pageTitle: 'Carregando...' }
},
beforeMount() {
// Simulando o retorno de uma API
setTimeout(() => {
const apiResponse = { title: 'Título da Página' }
this.pageTitle = apiResponse.title
}, 2000)
},
}
</script>
<template>
<h1>{{ pageTitle }}</h1>
</template>
No código acima, simulamos uma chamada a uma API que leva dois segundos para atualizar o título da página. O mesmo efeito seria alcançado utilizando created
.
mounted
O mounted
é executado imediatamente após o componente ser montado. Nesse estágio, o componente se torna funcional: as propriedades de data()
são injetadas no template, os elementos são renderizados e a manipulação do DOM se torna possível.
<script>
export default {
data() {
return {
items: [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' }
]
}
},
mounted() {
const listItems = this.$refs.listItem
listItems.forEach((item) => {
item.addEventListener('click', () => {
alert(`Você clicou em: ${item.textContent}`)
})
})
}
}
</script>
<template>
<ul>
<li v-for="item in items" :key="item.id" ref="listItem">
{{ item.name }}
</li>
</ul>
</template>
No exemplo acima, criamos uma lista dinâmica no template, mas os eventos de clique de cada <li>
da lista só foram inseridos após o mounted()
do componente.
Hooks de atualização
Os hooks de atualização são executados sempre que uma propriedade reativa usada no seu componente sofre uma alteração, forçando-o a re-renderizar. Essa execução pode ser imediatamente antes ou depois dessa alteração.
Aqui conheceremos os hooks beforeUpdate e updated.
beforeUpdate
O beforeUpdate
entra em execução imediatamente após uma propriedade reativa ser alterada, mas imediatamente antes do componente ser re-renderizado em tela.
<script>
export default {
data() {
return {
contador: 0,
historico: []
}
},
beforeUpdate() {
this.historico.push(this.contador)
},
methods: {
incrementar() {
this.contador++
}
}
}
</script>
No código acima, por exemplo, sempre que counter
for atualizado, salvaremos o seu novo valor no array historico
antes que esses dados sejam atualizados em tela. O beforeUpdate
também é muito utilizado para operações de debugging para identificar em que momento a renderização do componente é ativada, por exemplo.
updated
O updated
é executado imediatamente após uma re-renderização do componente causado por uma alteração de dados reativos, sendo bastante útil para criação de efeitos colaterais (side effects) relacionados ao DOM.
<script>
export default {
data() {
return {
items: []
}
},
methods: {
addItem(item) {
this.items.push(item)
}
},
updated() {
const listContainer = this.$refs.listContainer
listContainer.scrollTop = listContainer.scrollHeight
}
}
</script>
No código acima, sempre que um novo item é adicionado à lista usando o método addItem
, o componente é atualizado, e o método updated
é chamado. Dentro do método updated
, implementamos uma funcionalidade de rolagem automática para que a lista role para o último item sempre que novos itens forem adicionados à lista.
Hooks de desmontagem
Os hooks de desmontagem, também chamados de destruction hooks (meio dramático, não?) são executados imediatamente antes ou depois de um componente ser desmontado.
Aqui conheceremos os hooks beforeUnmount e unmounted.
beforeUnmount
O beforeUnmount
é executado imediatamente antes do componente ser desmontado. É importante ressaltar que, nesse ponto, o componente ainda está totalmente funcional, então é uma ótima etapa para executar funções de clean up, como remoção de event listeners, exclusão de variáveis, ações de logout, etc.
<script>
export default {
data() {
return {
socket: null
};
},
mounted() {
this.socket = new WebSocket('wss://example.com/socket');
this.socket.addEventListener('message', this.handleMessage);
},
beforeUnmount() {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.removeEventListener('message', this.handleMessage);
this.socket.close();
}
},
methods: {
handleMessage(event) {
// Lógica para lidar com mensagens WebSocket recebidas
}
}
};
</script>
Neste exemplo, o componente cria uma conexão WebSocket quando é montado e adiciona um ouvinte para mensagens WebSocket. No beforeUnmount
, ele verifica se essa conexão está aberta (WebSocket.OPEN
) e, se estiver, fecha a conexão usando this.socket.close()
. Isso garante que a conexão seja devidamente encerrada antes que o componente seja destruído, evitando vazamentos de recursos e comportamento inesperado.
unmounted
O unmounted
acontece imediatamente após o componente ser desmontado da árvore do DOM, então praticamente tudo que existia em relação a esse componente é considerado "destruído", como enfatizado pela própria documentação:
Um componente é considerado desmontado depois que:
Todos os seus componentes-filhos foram desmontados;
Todos os seus efeitos reativos associados (efeitos de renderização e dados criados durante
setup()
) foram parados.
Podemos concluir que, nesse estágio, não há muito o que ser feito em um componente. De forma geral, o unmounted
também é usado para funções de clean up.
Lifecycle hooks com Composition API
A sintaxe mais atual de um componente Vue — a Composition API —, trouxe algumas mudanças aos métodos de ciclo de vida, sendo as mais evidentes a exclusão dos hooks beforeCreate e created, além da mudança sutil nos seus nomes e sua sintaxe.
Essas mudanças vieram para favorecer a performance da nossa aplicação Vue, além de proporcionar uma melhor compatibilidade com TypeScript, e são possíveis graças ao novo hook setup()
.
Vamos entender um pouco mais sobre isso?
setup()
O hook setup()
no Vue 3 é uma parte essencial da Composition API, que é uma abordagem mais flexível, funcional e modular para a criação de componentes em comparação com a sintaxe do Vue 2. Ele é usado para configurar e inicializar o estado, props, métodos e outras opções de um componente Vue.
No entanto, apesar de ser uma feature da Composition API, é totalmente possível utilizar o setup()
dentro da Options API como facilitador para seu código. Veja como fica a sintaxe do hook em ambos os casos:
<!-- OPTIONS API -->
<script>
export default {
setup () {
console.log("Setup com Options API");
}
}
</script>
<!-- COMPOSITION API -->
<script setup>
console.log("Setup com Composition API");
</script>
Exclusão dos hooks beforeCreate e created
Se voltarmos ao diagrama de ciclos de vida que vimos no começo desse artigo, podemos perceber que o setup()
é o primeiro método de criação executado em um componente Vue. Ele ocorre antes mesmo da execução de beforeCreate e created!
Dessa forma, qualquer código que pudesse ser utilizado dentro desses hooks pode ser simplesmente inserido dentro do setup()
. Você se lembra da simulação de requisição de API que exemplificamos anteriormente? Veja como ela ficaria utilizando <script setup>
:
<script setup>
import {ref} from 'vue';
let pageTitle = ref('Carregando...');
setTimeout(() => {
const apiResponse = {title: 'Título da Página'};
pageTitle.value = apiResponse.title;
}, 2000);
</script>
Com o código acima, teremos o mesmo efeito que teríamos com created
ou com beforeMount
!
Sintaxe dos lifecycle hooks com Composition API
Apesar de não termos mais os hooks beforeCreate e created, os demais hooks continuam presentes na Composition API, porém com uma sintaxe um pouco diferente do que vimos, pois agora estaremos chamando cada um deles dentro de setup()
.
É importante ter em mente que o momento de chamada de cada um desses hooks dentro do componente permanece o mesmo! Logo, vamos focar aqui apenas na sintaxe deles, começando pela mudança nos nomes, que agora são: onBeforeMount
, onMounted
, onBeforeUpdate
, onUpdated
, onBeforeUnmount
e onUnmounted
.
Já para entendermos melhor a sintaxe de uso, basta entender que, na Composition API, cada hook é uma função que recebe como parâmetro uma callback function. Por exemplo, eis a tipagem do onMounted
:
function onMounted(callback: () => void): void
Com base nisso, podemos usar nossos hooks das seguintes formas:
<script setup>
import { ref, onMounted } from 'vue';
const myMessage = ref('Uma mensagem qualquer')
const showMessage = () => {
alert(myMessage.value);
};
// FORMA 1:
onMounted(showMessage);
// FORMA 2:
onMounted(() => showMessage());
// FORMA 3:
onMounted(() => {
showMessage()
}
</script>
Vamos entender as sutis diferenças entre cada uma delas:
- Forma 1:
onMounted(showMessage)
Nesta forma, você está passando diretamente a função showMessage
como um argumento para onMounted
. A função showMessage
será chamada diretamente quando onMounted
for executado. Isso é útil quando a função showMessage
não precisa de argumentos adicionais.
- Forma 2:
onMounted(() => showMessage())
Aqui, você está envolvendo a chamada de showMessage
em uma função de callback anônima. A função de callback será executada quando onMounted
for acionado e, em seguida, ela chamará showMessage()
. Esta forma é útil quando você precisa passar argumentos para showMessage
.
- Forma 3:
onMounted(() => { showMessage() })
Essa forma é semelhante à forma 2, mas você está usando uma função de callback anônima que inclui um bloco de código entre chaves { }
. Isso é útil quando você precisa realizar várias ações ou lógica dentro do onMounted
além de chamar showMessage
.
A principal diferença entre as formas 2 e 3 é que a forma 3 permite que você inclua várias linhas de código e realize várias ações dentro do onMounted
. A forma 2 é mais concisa e direta, enquanto a forma 3 é mais expansível quando você precisa de mais complexidade.
A sintaxe acima pode ser utilizada com qualquer hook da Composition API, não apenas com onMounted
. Além disso, você pode utilizar um mesmo lifecycle hook várias vezes dentro de um único componente! Também note que é importante que os hooks que você quer utilizar no seu componente precisam ser importados dentro do <script setup>
:
import { onMounted, onUpdated } from 'vue';
Essa é mais uma particularidade da Composition API: ao importar da API somente o que é necessário para cada componente, melhoramos a performance da nossa aplicação, ao contrário do que acontecia na Options API, em que toda a API já era totalmente importada nos componentes por baixo dos panos.
Conclusão
Nesse artigo aprendemos um pouco mais sobre os vários métodos de ciclo de vida que o Vue.JS oferece, conhecendo quando e como aplicá-los para melhorar nossas aplicações.
Espero que tenham gostado do conteúdo e que você tenha aprofundado ainda mais seu conhecimento em Vue. Até a próxima!