JSX e Componentes
Aprenda a sintaxe do JSX e crie seus primeiros componentes React tipados com TypeScript.
No capítulo anterior, você aprendeu a manipular o DOM diretamente: selecionar elementos com document.querySelector, escutar eventos com addEventListener, e atualizar a tela manualmente toda vez que algo muda. Funciona — mas você já deve ter sentido a dor. Em uma interface pequena, com um botão e um parágrafo, não tem problema nenhum. Mas imagine um dashboard de finanças pessoais de verdade: uma lista de gastos que cresce a cada novo lançamento, um total que precisa ser recalculado sempre, um formulário com validação, um filtro por categoria, um resumo que muda de cor dependendo do saldo. Se você tentar construir isso manipulando o DOM na mão, rapidamente vai se perder em um emaranhado de getElementById, variáveis soltas tentando representar "o estado atual da tela" e funções de atualização espalhadas por todo canto — cada uma responsável por lembrar de atualizar uma parte diferente da interface.
É exatamente esse problema que frameworks front-end como o React resolvem. Nesta seção você vai entender por que eles existem, o que cada um propõe, e vai escrever suas primeiras interfaces reais com React e TypeScript — a combinação mais usada no mercado brasileiro hoje.
O problema: manipular o DOM na mão não escala
Pense em como seria adicionar um gasto ao seu Rastreador do Starter, mas agora numa página web em vez de um terminal. Toda vez que um gasto é adicionado, várias partes da tela precisam mudar ao mesmo tempo: a lista de gastos, o total, o resumo por categoria. Manipulando o DOM manualmente, o código ficaria mais ou menos assim:
// Abordagem manual: cada mudança de estado exige lembrar de atualizar a tela em vários lugares
let gastos: { descricao: string; valor: number }[] = [];
function adicionarGasto(descricao: string, valor: number): void {
gastos.push({ descricao, valor });
// Preciso lembrar de atualizar a lista na tela...
const lista = document.querySelector("#lista-gastos")!;
lista.innerHTML = gastos
.map((g) => `<li>${g.descricao}: R$ ${g.valor.toFixed(2)}</li>`)
.join("");
// ...e também o total. Se eu esquecer essa linha, a tela fica desatualizada
// e ninguém vai perceber até um usuário reclamar.
const totalElemento = document.querySelector("#total")!;
const total = gastos.reduce((acc, g) => acc + g.valor, 0);
totalElemento.textContent = `R$ ${total.toFixed(2)}`;
}Esse código funciona, mas ele é imperativo: você descreve, passo a passo, como atualizar a tela. Toda nova funcionalidade (um botão de remover, um filtro, uma edição) significa lembrar de encontrar e atualizar mais um pedaço do DOM manualmente. Em uma tela real, com dezenas de elementos interdependentes, isso vira um campo minado — é fácil esquecer de atualizar algum lugar, e o bug resultante ("a tela mostra dados desatualizados") é difícil de rastrear.
Frameworks front-end resolvem isso mudando a pergunta. Em vez de "como eu atualizo a tela quando o estado muda?", você passa a perguntar apenas "qual é a aparência da tela para este estado?". Essa é a ideia de programação declarativa: você descreve o resultado desejado, e o framework se encarrega de descobrir as mudanças necessárias no DOM.
O que são frameworks front-end
Um framework front-end é uma ferramenta que dá estrutura para construir interfaces de forma declarativa, cuidando da sincronização entre estado (os dados que mudam ao longo do tempo) e UI (o que é exibido na tela) para você. Na prática, isso costuma significar três coisas:
- Componentização: a interface é dividida em pedaços independentes e reutilizáveis (um
Botao, umCartaoGasto, umaListaDeGastos), cada um responsável pela própria aparência e comportamento. - Reatividade: quando o estado de um componente muda, o framework atualiza automaticamente apenas as partes da tela que dependem dele — sem você precisar escrever código de atualização manual.
- Renderização declarativa: você escreve algo como "se
gastosestiver vazio, mostre esta mensagem; senão, mostre esta lista", e o framework decide como transformar isso em operações reais no DOM.
O termo "framework" e "biblioteca" às vezes são usados quase como sinônimos no ecossistema front-end. Tecnicamente, o React é uma biblioteca — ele cuida só da camada de UI, e você escolhe outras ferramentas para rotas, chamadas de API, etc. Angular, por outro lado, é um framework completo, com todas essas peças incluídas. Isso não muda o problema que ambos resolvem, mas explica por que a experiência de usar cada um é tão diferente.
Panorama dos principais frameworks
Não existe "o melhor framework" de forma absoluta — existem ferramentas com propostas diferentes, cada uma com vantagens e contextos onde brilha mais. Vale conhecer, ao menos superficialmente, os quatro nomes que você mais vai ouvir no mercado:
| Framework | Panorama rápido |
|---|---|
| React | Mantido pelo Meta (Facebook), o React é uma biblioteca focada exclusivamente na camada de UI. Usa JSX (uma sintaxe que mistura HTML e JavaScript/TypeScript) e um modelo de componentes funcionais com hooks — funções especiais que adicionam estado e comportamento aos componentes. Tem o maior ecossistema de bibliotecas do mercado e é, hoje, o framework com mais vagas de emprego abertas no Brasil e no mundo. |
| Vue | Um framework progressivo: você pode adotá-lo aos poucos, desde um script simples até uma aplicação completa. Sua sintaxe de templates é mais parecida com HTML puro, usando diretivas como v-if e v-for em vez de JSX. Tem curva de aprendizado suave e uma Composition API (bem parecida com os hooks do React) para organizar lógica reutilizável. |
| Angular | Mantido pelo Google, é um framework completo: já vem com roteamento, injeção de dependência, formulários e chamadas HTTP embutidos. Foi construído em TypeScript desde o início, o que o torna extremamente tipado e estruturado — mas também mais verboso e com uma curva de aprendizado mais íngreme. É comum em empresas grandes e sistemas corporativos legados. |
| Svelte | Diferente dos outros três, o Svelte não roda uma biblioteca no navegador em tempo de execução — ele é um compilador. Você escreve um código muito próximo de JavaScript/TypeScript puro, e o Svelte o transforma, em tempo de build, em código imperativo altamente otimizado, sem depender de um "virtual DOM". O resultado são bundles menores e ótima performance, mas com uma comunidade e mercado de trabalho ainda menores no Brasil. |
Por que este curso ensina React
A escolha não é arbitrária. Três razões concretas guiaram essa decisão:
- Demanda de mercado. No Brasil, a esmagadora maioria das vagas de front-end pede React. Aprender React é, hoje, o caminho mais direto para sua primeira oportunidade como desenvolvedor front-end.
- Suporte a TypeScript de primeira classe. A comunidade React investiu pesado em tipagem — componentes, props, hooks e eventos são tipados com clareza, o que combina perfeitamente com tudo que você já aprendeu no Starter.
- Caminho de crescimento natural. Este curso é o primeiro de uma trilha (Júnior → Pleno → Sênior) que evolui com Next.js, um meta-framework construído sobre React. Aprender React bem agora significa que, no Pleno, você vai aprofundar o mesmo modelo mental, em vez de aprender algo do zero.
Isso não significa que Vue, Angular ou Svelte sejam escolhas ruins — são ótimas ferramentas, e nada impede você de explorá-las mais adiante. Mas para começar sua carreira com o maior número possível de portas abertas, React é a aposta mais segura.
Agora que você entende por que o React existe, é hora de escrever o primeiro pedaço de interface de verdade. Tudo em React começa com dois conceitos que andam juntos: JSX, a sintaxe usada para descrever a UI, e componentes, as unidades reutilizáveis que a organizam. Vamos construir, ao longo deste capítulo, as primeiras peças do dashboard de finanças que vai crescer com você até o Capítulo 6.
O que é JSX
JSX (JavaScript XML) é uma extensão de sintaxe que permite escrever algo que parece HTML diretamente dentro do seu código TypeScript. Não é uma linguagem nova — é "açúcar sintático" que o compilador (o mesmo Next.js/bundler que você já configurou) transforma em chamadas de função JavaScript comuns por trás dos panos.
const elemento = <h1>Olá, MB Academy!</h1>;Apesar da aparência, existem diferenças importantes entre JSX e HTML puro:
- Um único elemento raiz. Um bloco JSX precisa retornar um único elemento "pai" (ou um Fragment, que veremos adiante).
classNameem vez declass. Comoclassé uma palavra reservada em JavaScript, o JSX usaclassNamepara aplicar classes CSS.- Atributos em camelCase. Eventos e atributos que em HTML são
onclickoutabindexviramonClicketabIndexem JSX. - Expressões entre chaves
{}. Qualquer expressão TypeScript válida — uma variável, uma conta, uma chamada de função — pode ser inserida no meio do JSX usando chaves.
const nomeUsuario = "Marina";
const totalGasto = 245.9;
const saudacao = (
<div className="cartao">
<p>Olá, {nomeUsuario}!</p>
<p>Você já gastou R$ {totalGasto.toFixed(2)} este mês.</p>
</div>
);Tudo dentro das chaves é avaliado como TypeScript comum. Isso significa que você pode colocar qualquer expressão ali — mas não comandos como if ou for diretamente (isso é resolvido com técnicas que veremos no próximo capítulo, sobre renderização condicional e listas).
JSX não aceita mais de um elemento raiz retornado sem um "envelope". Se
precisar retornar dois elementos irmãos sem adicionar uma div extra ao
DOM, use um Fragment: <>...</>. Ele agrupa elementos sem gerar
nenhuma tag real na página renderizada.
Criando seu primeiro componente
Um componente em React é, na essência, uma função TypeScript que retorna JSX. Por convenção, o nome do componente começa com letra maiúscula (PascalCase) — isso é o que diferencia, para o React, uma tag de componente (<CartaoGasto />) de uma tag HTML nativa (<div />). Arquivos que contêm JSX usam a extensão .tsx em vez de .ts.
Vamos criar o primeiro componente do nosso dashboard: um cartão que exibe um único gasto.
function CartaoGasto() {
return (
<div className="cartao-gasto">
<p>Almoço no restaurante</p>
<strong>R$ 45,50</strong>
</div>
);
}Para usar esse componente em outra parte da aplicação, você o "renderiza" como se fosse uma tag:
function App() {
return (
<div>
<h2>Meus gastos</h2>
<CartaoGasto />
</div>
);
}O componente CartaoGasto funciona, mas tem um problema óbvio: os dados ("Almoço no restaurante", "R$ 45,50") estão fixos dentro dele. Se quisermos usar esse mesmo componente para exibir gastos diferentes, precisamos de uma forma de passar dados de fora para dentro. É para isso que existem as props.
Props: dados de fora para dentro
Props (abreviação de "properties") são a forma como um componente pai passa dados para um componente filho — de forma parecida com argumentos de uma função. Em TypeScript, começamos definindo o formato esperado dessas props com uma interface ou type:
type Categoria =
| "alimentação"
| "transporte"
| "moradia"
| "lazer"
| "saúde"
| "outros";
interface CartaoGastoProps {
descricao: string;
valor: number;
categoria: Categoria;
}
function CartaoGasto({ descricao, valor, categoria }: CartaoGastoProps) {
return (
<div className="cartao-gasto">
<p>{descricao}</p>
<span>{categoria}</span>
<strong>R$ {valor.toFixed(2)}</strong>
</div>
);
}Repare que recebemos as props já desestruturadas diretamente no parâmetro da função — esse é o padrão mais comum em componentes React. Agora, cada vez que usarmos <CartaoGasto />, podemos (e devemos) passar valores diferentes:
function App() {
return (
<div>
<h2>Meus gastos</h2>
<CartaoGasto
descricao="Almoço no restaurante"
valor={45.5}
categoria="alimentação"
/>
<CartaoGasto
descricao="Passagem de metrô"
valor={4.4}
categoria="transporte"
/>
</div>
);
}Note a diferença de sintaxe: descricao="..." usa aspas porque é uma string literal, enquanto valor={45.5} usa chaves porque é uma expressão TypeScript (um número). Essa mesma distinção — aspas para strings literais, chaves para qualquer outra expressão — vale para qualquer prop.
Se o TypeScript reclamar que uma prop obrigatória não foi passada, ou que o tipo está errado (por exemplo, passar categoria="viagem", que não existe no union type Categoria), é exatamente esse o benefício de tipar props: o erro aparece antes de o código rodar, direto no seu editor.
Props opcionais e valores padrão
Nem toda prop precisa ser obrigatória. Assim como em funções, podemos marcar uma prop como opcional com ? e, dentro do componente, atribuir um valor padrão via desestruturação:
interface CartaoGastoProps {
descricao: string;
valor: number;
categoria: Categoria;
pago?: boolean; // opcional — nem todo gasto precisa informar isso
}
function CartaoGasto({
descricao,
valor,
categoria,
pago = false, // valor padrão caso a prop não seja passada
}: CartaoGastoProps) {
return (
<div className="cartao-gasto">
<p>{descricao}</p>
<span>{categoria}</span>
<strong>R$ {valor.toFixed(2)}</strong>
{pago && <span className="selo-pago">Pago</span>}
</div>
);
}Assim, <CartaoGasto descricao="Aluguel" valor={1200} categoria="moradia" pago /> exibe o selo, enquanto omitir a prop pago mantém o padrão false. (A expressão {pago && <span>...} é uma técnica de renderização condicional que vamos explorar em detalhes no próximo capítulo.)
Composição de componentes e children
Um dos maiores ganhos do modelo de componentes é a composição: construir componentes complexos combinando componentes menores e mais simples, da mesma forma que você combina funções pequenas para montar uma lógica maior.
Vamos criar um componente genérico Cartao, que serve de "moldura" reutilizável para qualquer conteúdo — não só gastos:
import type { ReactNode } from "react";
interface CartaoProps {
titulo: string;
children: ReactNode;
}
function Cartao({ titulo, children }: CartaoProps) {
return (
<div className="cartao">
<h3>{titulo}</h3>
{children}
</div>
);
}A prop especial children representa tudo que é colocado entre as tags de abertura e fechamento do componente ao usá-lo — é assim que o React permite compor conteúdo aninhado, igual você faz com tags HTML comuns como <div>...</div>.
function ResumoMensal() {
return (
<Cartao titulo="Resumo do mês">
<p>Total gasto: R$ 245,90</p>
<p>Maior categoria: alimentação</p>
</Cartao>
);
}Aqui, o parágrafo com o total e o parágrafo com a maior categoria são passados como children para Cartao, que não sabe (nem precisa saber) o que está renderizando dentro de si — apenas empresta sua "moldura" visual. Esse padrão de composição é o que permite montar interfaces complexas a partir de peças pequenas e independentes, exatamente como você fazia com funções puras no Starter.
Um erro comum ao começar com JSX é tentar controlar fluxo com if/for
diretamente dentro do bloco retornado, como em HTML com um template engine.
Isso não funciona — JSX aceita apenas expressões, não comandos. Uma
chamada de função, um ternário, um &&: tudo bem. Um if solto dentro do
return (...): erro de sintaxe. Vamos ver as alternativas corretas no
próximo capítulo.
Para praticar
Vamos criar um segundo componente que reaproveita o Cartao genérico construído neste capítulo, aplicando composição e props opcionais.
Cenário: O dashboard precisa de um cartão de resumo que destaque um valor total, com uma cor de destaque configurável.
Requisitos:
- Crie uma interface
CartaoResumoPropscomtitulo: string,valor: numberecorDestaque?: string(opcional). - Crie o componente
CartaoResumo, que usa o valor padrão"#2563eb"paracorDestaquecaso a prop não seja informada. - Dentro do componente, formate
valorcomo moeda, exibindoR$seguido devalor.toFixed(2). - Utilize o componente
Cartao(criado neste capítulo) para envolver o conteúdo, passandotituloadiante e o valor formatado comochildren, estilizado com a cor de destaque usando o atributostyle={{ color: corDestaque }}. - Renderize dois
CartaoResumo: um informandocorDestaque="#16a34a"e outro sem informar nada (usando o padrão azul).
Clique para ver uma possível solução
interface CartaoResumoProps {
titulo: string;
valor: number;
corDestaque?: string;
}
function CartaoResumo({
titulo,
valor,
corDestaque = "#2563eb",
}: CartaoResumoProps) {
return (
<Cartao titulo={titulo}>
<strong style={{ color: corDestaque }}>R$ {valor.toFixed(2)}</strong>
</Cartao>
);
}
function App() {
return (
<div>
<CartaoResumo titulo="Receita do mês" valor={3800} corDestaque="#16a34a" />
<CartaoResumo titulo="Total gasto" valor={1245.9} />
</div>
);
}O primeiro CartaoResumo recebe corDestaque="#16a34a" (verde) explicitamente. O segundo omite a prop, e o valor padrão "#2563eb" (azul) definido na desestruturação é usado automaticamente — sem precisar de nenhum if para isso.
Você acabou de escrever seus primeiros componentes React tipados: entendeu a sintaxe do JSX, criou componentes funcionais, definiu props com interface, usou valores padrão e compôs componentes com children. Isso é o esqueleto de qualquer interface React. No próximo capítulo, vamos dar vida a esses componentes: aprender a guardar estado que muda ao longo do tempo e reagir a eventos do usuário — a peça que faltava para transformar um cartão estático em um dashboard de verdade.