Projeto guiado: lista de tarefas em JS puro
Junta DOM, eventos, array e async num único mini-app — sem framework nenhum.
Sem React, sem Next.js — só o que as 7 lições anteriores já cobriram. O objetivo é sentir, na mão, o trabalho manual que um framework resolve por você (e é exatamente o trabalho que a trilha de Frontend vai automatizar a partir da próxima trilha).
1. O estado — um array simples guarda tudo
let tarefas = []; // cada item: { id, titulo, feita }
function adicionarTarefa(titulo) {
tarefas.push({ id: Date.now(), titulo, feita: false });
renderizar();
}
function alternarFeita(id) {
tarefas = tarefas.map((t) => (t.id === id ? { ...t, feita: !t.feita } : t));
renderizar();
}
function removerTarefa(id) {
tarefas = tarefas.filter((t) => t.id !== id);
renderizar();
}Repare o padrão: nenhuma dessas funções mexe direto no DOM. Elas só atualizam o array tarefas e chamam renderizar() — quem sabe desenhar a tela é uma função só, separada da lógica de dado. Esse é o mesmo princípio que o React formaliza (estado muda → tela é redesenhada a partir do estado).
2. Renderizar — reconstruir o DOM a partir do array
const lista = document.querySelector("#lista-tarefas");
function renderizar() {
lista.innerHTML = ""; // limpa tudo — abordagem simples, um framework otimiza isso
for (const tarefa of tarefas) {
const item = document.createElement("li");
item.textContent = tarefa.titulo;
item.style.textDecoration = tarefa.feita ? "line-through" : "none";
const botaoConcluir = document.createElement("button");
botaoConcluir.textContent = tarefa.feita ? "Desfazer" : "Concluir";
botaoConcluir.addEventListener("click", () => alternarFeita(tarefa.id));
const botaoRemover = document.createElement("button");
botaoRemover.textContent = "Remover";
botaoRemover.addEventListener("click", () => removerTarefa(tarefa.id));
item.append(botaoConcluir, botaoRemover);
lista.appendChild(item);
}
}3. O formulário
const form = document.querySelector("#form-nova-tarefa");
const input = document.querySelector("#input-titulo");
form.addEventListener("submit", (evento) => {
evento.preventDefault(); // impede o recarregamento padrão da página
if (input.value.trim() === "") return;
adicionarTarefa(input.value.trim());
input.value = "";
});4. Carregando do "servidor" (simulado com async)
async function carregarTarefasIniciais() {
// simula uma API real, que levaria um tempo pra responder
const resposta = await new Promise((resolve) =>
setTimeout(() => resolve([{ id: 1, titulo: "Estudar JS", feita: false }]), 500),
);
tarefas = resposta;
renderizar();
}
carregarTarefasIniciais();Exercício
Adicione um contador que mostra "X de Y tarefas concluídas" acima da lista, atualizado toda vez que renderizar() roda.