DevPath · Aprenda a programar ESPTEN

React profissional: TypeScript, SSR e acessibilidade

React + TypeScript: tipar seus componentes

Por que TypeScript no React

Em projetos profissionais, o React quase sempre é escrito com TypeScript. A vantagem é enorme: o editor avisa sobre erros antes de executar (uma prop mal escrita, um valor que pode ser undefined, um handler com o tipo errado), e o código se autodocumenta. Para o React, o mais importante é tipar as props, o estado e os eventos.

Nota: o TypeScript é compilado para JavaScript; no navegador sempre acaba rodando JS. Aqui veremos a teoria com exemplos .tsx; os exercícios práticos deste módulo são escritos em JS válido.

Tipar as props com interface ou type

As props de um componente são um objeto, então descrevemos seu formato com uma interface (ou um type). Depois a usamos para tipar o parâmetro:

interface SaudacaoProps {
  nome: string;
  idade?: number; // o "?" a torna opcional
}

function Saudacao({ nome, idade }: SaudacaoProps) {
  return <h1>Olá, {nome}{idade ? ` (${idade})` : ""}</h1>;
}

interface e type são quase intercambiáveis para props; use o que sua equipe preferir. type também permite uniões (type Estado = "ok" | "erro").

Componentes: função tipada ou React.FC

A forma recomendada hoje é uma função normal com as props tipadas (como acima). Também existe React.FC, que tipa o componente inteiro e inclui children de forma implícita em versões antigas:

const Botao: React.FC<{ texto: string }> = ({ texto }) => {
  return <button>{texto}</button>;
};

No React moderno se prefere a função tipada em vez de React.FC, porque é mais explícita (você declara children só se precisar) e mais flexível.

Estado tipado: useState<T>

useState infere o tipo a partir do valor inicial:

const [nome, setNome] = useState("");       // string
const [ativo, setAtivo] = useState(false);  // boolean

Quando o valor inicial não basta para inferir (p. ex. começa em null mas depois guardará um objeto), você anota explicitamente com useState<T>:

interface Usuario { id: number; nome: string; }

const [usuario, setUsuario] = useState<Usuario | null>(null);

Tipar eventos

Os manipuladores de eventos recebem um evento do React, não do DOM nativo. Os tipos vivem no espaço React.*:

function Campo() {
  const [texto, setTexto] = useState("");

  function aoDigitar(e: React.ChangeEvent<HTMLInputElement>) {
    setTexto(e.target.value);
  }

  function aoEnviar(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();
  }

  return (
    <form onSubmit={aoEnviar}>
      <input value={texto} onChange={aoDigitar} />
    </form>
  );
}

Os mais comuns: React.ChangeEvent<HTMLInputElement> (inputs), React.MouseEvent<HTMLButtonElement> (cliques), React.FormEvent (formulários) e React.KeyboardEvent (teclado). Se você escrever o handler em linha (onClick={(e) => ...}), o TypeScript geralmente infere o tipo do evento por você.

children e React.ReactNode

children é "o que vai entre as tags de abertura e fechamento" do componente. Como pode ser texto, um número, um elemento JSX, uma lista deles ou nada, seu tipo é React.ReactNode: o tipo mais geral de "qualquer coisa que o React pode renderizar".

interface CartaoProps {
  titulo: string;
  children: React.ReactNode;
}

function Cartao({ titulo, children }: CartaoProps) {
  return (
    <section>
      <h2>{titulo}</h2>
      <div>{children}</div>
    </section>
  );
}

// Uso:
<Cartao titulo="Olá">
  <p>Qualquer conteúdo vai aqui.</p>
</Cartao>

Regra mental: use React.ReactNode para children e para qualquer prop que receba "conteúdo renderizável". Para uma prop que seja um elemento específico, existe React.ReactElement.

Coloque isto em prática

O DevPath é um curso prático: aqui você lê a teoria; no app você a coloca em prática com exercícios que rodam de verdade, offline.

Comece grátis no app →
Renderização no servidor: CSR, SSR, SSG e Server Components →