Estado e Reatividade no Vue 3
Quando você usa o Instagram e o número de curtidas muda sem recarregar a página, ou adiciona um produto ao carrinho e o ícone atualiza na hora — por baixo está acontecendo algo simples: um dado mudou e a tela reagiu.
Para construir interfaces assim, dois conceitos precisam estar claros antes de qualquer código:
Estado é o conjunto de dados que o app precisa acompanhar ao longo da sessão do usuário: quais séries estão carregadas, o texto digitado num campo de busca, se um formulário está aberto ou fechado. Toda interface interativa tem estado — a questão é como gerenciá-lo.
Reatividade é o mecanismo que mantém a tela sincronizada com esse estado automaticamente. Em vez de você manipular o DOM na mão toda vez que um valor muda, você declara o estado, escreve o template usando ele e o Vue assume a responsabilidade de atualizar apenas as partes da tela que dependem daquele dado.
Mas para o sistema de reatividade do Vue funcionar, ele precisa saber que um valor mudou — e JavaScript puro não avisa ninguém quando uma variável é reatribuída. Por isso o Vue oferece ferramentas próprias para declarar estado: ref e computed. É o que você vai aprender aqui.
1. O problema — por que variável comum não funciona
Em JavaScript, quando você altera o valor de uma variável, o navegador não faz nada automaticamente — a tela não atualiza sozinha.
O Vue precisa saber que um valor mudou para re-renderizar o template. Uma variável let comum é invisível para o Vue.
<!-- ❌ Isso não funciona — a tela nunca atualiza -->
<template>
<p>{{ contador }}</p>
<button @click="incrementar">+1</button>
</template>
<script setup>
let contador = 0;
function incrementar() {
contador++; // Vue não sabe que isso mudou
}
</script>
A solução é embrulhar o valor em um objeto reativo usando ref().
2. ref() — tornando um valor reativo
A solução é envolver o valor dentro de um objeto especial que o Vue sabe observar. Toda vez que você modifica o conteúdo desse objeto, o Vue detecta a mudança e re-renderiza o template. Pense nele como uma caixa transparente: o Vue fica de olho na caixa, e quando o conteúdo muda, o Vue age.
Esse objeto é criado com ref(). Quando o valor interno muda, o template re-renderiza automaticamente.
<!-- ✅ Funciona — Vue observa o ref e atualiza a tela -->
<template>
<p>{{ contador }}</p>
<button @click="incrementar">+1</button>
</template>
<script setup>
import { ref } from "vue";
const contador = ref(0);
function incrementar() {
contador.value++; // .value é obrigatório no script
}
</script>
Regra importante: no <script setup>, acesse o valor com .value. No <template>, o Vue desempacota automaticamente — escreva apenas {{ contador }}.
const nome = ref("Ana");
console.log(nome.value); // "Ana"
nome.value = "Carlos"; // atualiza e re-renderiza
Use ref() para qualquer tipo de dado: string, número, boolean, array ou objeto.
const itens = ref([]); // array vazio
const ativo = ref(false); // boolean
const produto = ref(null); // começa sem valor
3. reactive() — você vai encontrar isso
Você vai encontrar reactive() em tutoriais e projetos existentes. Ele também torna objetos reativos, mas sem .value — as propriedades são acessadas diretamente (form.titulo em vez de form.value.titulo).
A limitação prática é que reactive() perde a reatividade se você substituir o objeto inteiro ou desestruturar suas propriedades fora do template. Por isso, ref() é a escolha padrão no Vue 3 moderno — funciona com qualquer tipo de valor e não tem essas armadilhas.
Neste curso usamos
ref()para tudo.
4. computed() — propriedades derivadas
Pense numa planilha: você tem uma coluna de preço, outra de quantidade e uma terceira de total. Você não preenche o total manualmente — ele é uma fórmula que calcula automaticamente a partir das outras colunas. Se o preço muda, o total atualiza sozinho.
computed é exatamente esse mecanismo no Vue: um valor que deriva de outros valores reativos. Você define a regra uma vez e o Vue mantém o resultado atualizado. O resultado também é armazenado em cache — se as dependências não mudaram desde o último cálculo, o Vue reutiliza o valor já calculado em vez de rodar a função de novo.
<template>
<p>Total de séries: {{ totalSeries }}</p>
<p>Séries assistidas: {{ totalAssistidas }}</p>
</template>
<script setup>
import { ref, computed } from "vue";
const series = ref([]);
const totalSeries = computed(() => series.value.length);
const totalAssistidas = computed(() => {
let contador = 0;
for (const serie of series.value) {
if (serie.assistida) contador++;
}
return contador;
});
</script>
computed vs método comum:
// computed — só recalcula quando a dependência muda
const totalSeries = computed(() => series.value.length);
const totalAssistidas = computed(() => {
let contador = 0;
for (const serie of series.value) {
if (serie.assistida) contador++;
}
return contador;
});
// método — recalcula toda vez que o template re-renderiza
function getTotalSeries() {
return series.value.length;
}
Use computed quando o valor depende de estado reativo e pode ser cacheado.
5. onMounted() — executar código ao carregar o componente
O ciclo de vida de um componente
Todo componente Vue passa por etapas previsíveis desde que é exibido até ser removido da tela:
- Criação — o Vue processa o
<script setup>, inicializa variáveis e estado reativo - Montagem — o Vue gera o HTML a partir do template e insere na página (o componente aparece na tela)
- Atualização — sempre que o estado muda, o template re-renderiza automaticamente
- Desmontagem — o componente é removido da tela (por exemplo, ao navegar para outra rota)
O Vue oferece funções especiais — chamadas de hooks de ciclo de vida — que permitem executar código em momentos específicos desse processo. Você registra um callback e o Vue o chama na hora certa.
onMounted é o hook da etapa 2: executa uma função uma única vez, logo após o componente aparecer na tela.
onMounted(() => {
// executa uma vez, quando o componente for inserido na página
});
Por que onMounted é o lugar certo para buscar dados de uma API?
A ideia é simples: o componente aparece primeiro (com a lista vazia ou um texto de “Carregando…”), depois o onMounted dispara o fetch. Quando os dados chegam, o estado é atualizado e o Vue re-renderiza automaticamente.
<template>
<ul>
<li v-for="musica in musicas" :key="musica.id">
{{ musica.titulo }}
</li>
</ul>
</template>
<script setup>
import { ref, onMounted } from "vue";
const musicas = ref([]); // 1. começa vazio — lista aparece vazia
onMounted(async () => {
// 2. dispara após o componente aparecer
const res = await fetch("https://api.exemplo.com/musicas");
musicas.value = await res.json(); // 3. atualiza o estado → Vue re-renderiza
});
</script>
Se você colocasse o fetch diretamente no topo do <script setup> (fora do onMounted), funcionaria na maioria dos casos simples — mas o onMounted é a forma idiomática do Vue, deixa explícito o momento em que a operação acontece e evita problemas em situações mais complexas (acesso ao DOM, componentes filhos, plugins).
6. Consumo de API REST com fetch
Até aqui, vimos como o estado muda no front-end. Agora entra o próximo passo: buscar e enviar dados para um backend.
O que é uma API REST?
Uma API REST (Representational State Transfer) é uma forma padronizada de comunicação entre cliente (front-end) e servidor via HTTP.
Na prática:
- o cliente faz uma requisição HTTP
- o servidor devolve uma resposta HTTP
- os dados geralmente trafegam em JSON
Em REST, trabalhamos com recursos (products, users, posts, orders etc.) e cada recurso costuma ter uma URL base (endpoint), por exemplo: /products.
Métodos HTTP mais comuns
- GET: ler dados
- POST: criar novo recurso
- PUT/PATCH: atualizar recurso existente
- DELETE: remover recurso
Em REST, a intenção da operação vem de método + URL.
Exemplos:
GET /products-> listar produtosGET /products/10-> buscar produto id 10POST /products-> criar produtoDELETE /products/10-> remover produto id 10
Requisição e resposta
Uma requisição HTTP costuma ter:
- URL: destino
- method: ação
- headers: metadados (ex.: tipo de conteúdo)
- body: dados enviados (quando necessário)
Uma resposta HTTP costuma ter:
- status code (ex.: 200, 201, 404, 500)
- headers
- body com os dados retornados (muitas vezes JSON)
Status comuns em APIs REST:
200 OK-> sucesso em leitura/atualização201 Created-> recurso criado204 No Content-> sucesso sem corpo de resposta (comum em DELETE)400 Bad Request-> dados inválidos404 Not Found-> recurso não encontrado500 Internal Server Error-> erro no servidor
Fluxo de formulário -> requisição -> resposta JSON
Fluxo típico de cadastro:
- usuário preenche um formulário
- front-end monta um objeto JS com os dados
fetchenviaPOSTcombody- API processa e responde JSON
- front converte com
await res.json()e atualiza o estado
Esse ciclo é a base do consumo de API no front-end.
O papel do JSON no envio de dados
O body da requisição HTTP é texto. Como no front trabalhamos com objetos JS, precisamos converter antes de enviar.
Para isso usamos JSON.stringify(...).
const produto = { nome: "Teclado", preco: 199.9, emEstoque: true };
console.log(produto);
// objeto JavaScript
const payload = JSON.stringify(produto);
console.log(payload);
// "{\"nome\":\"Teclado\",\"preco\":199.9,\"emEstoque\":true}"
Ao receber a resposta, fazemos o caminho inverso com res.json().
const res = await fetch("https://api.exemplo.com/products");
const dados = await res.json();
// dados volta a ser objeto/array JavaScript
Exemplo simples com fetch
async function criarProduto(produto) {
const res = await fetch("https://api.exemplo.com/products", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(produto),
});
if (!res.ok) throw new Error("Erro ao criar produto");
return await res.json();
}
async function removerProduto(id) {
const res = await fetch(`https://api.exemplo.com/products/${id}`, {
method: "DELETE",
});
if (!res.ok) throw new Error("Erro ao remover produto");
}
@submit.prevent e event.preventDefault()
Em formulários HTML, o envio padrão recarrega a página.
Em JavaScript puro, vocês já viram:
form.addEventListener("submit", (event) => {
event.preventDefault();
salvar();
});
No Vue, a ideia é exatamente a mesma, só muda a sintaxe:
<form @submit.prevent="salvar">
O .prevent é atalho para event.preventDefault().
7. Referência rápida
| Recurso | Importação | Uso |
|---|---|---|
ref(valor) | import { ref } from "vue" | Estado reativo simples |
computed(() => ...) | import { computed } from "vue" | Valor derivado de estado |
onMounted(() => ...) | import { onMounted } from "vue" | Executar ao montar o componente |
.value | — | Acessar/modificar ref no script |