Pular para o conteúdo
af aprenda frontend
módulo 05 tipos

TypeScript em projetos reais.

Tipando respostas de API, as const, declarações ambientes (.d.ts), evitando any, lidando com bibliotecas sem tipos.

As lições anteriores cobriram o sistema de tipos em ambiente controlado — você define interfaces, anota funções e o compilador valida tudo. Em projetos reais, há situações onde a linha entre TypeScript e o mundo externo é mais difusa: dados de APIs, bibliotecas JavaScript sem tipos, configurações que precisam de tipos literais. Esta lição cobre essas situações com o blog como exemplo concreto.

Tipando respostas de API

O ponto mais delicado: response.json() retorna Promise<any>. TypeScript aceita qualquer cast sobre any sem reclamar — você pode declarar que recebeu Artigo[] e o compilador acredita, mesmo que o servidor envie outra coisa.

ts
import type { Artigo } from "./types.ts";

// abordagem ingênua — cast direto, sem verificação
async function buscarArtigos(): Promise<Artigo[]> {
  const response = await fetch("/artigos.json");
  if (!response.ok) throw new Error(`Erro ${response.status}`);
  return response.json() as Promise<Artigo[]>;
  // TypeScript acredita — se o servidor enviar formato errado, o erro aparece em runtime
}

// abordagem mais honesta — cast explícito com variável intermediária
async function buscarArtigos(): Promise<Artigo[]> {
  const response = await fetch("/artigos.json");
  if (!response.ok) throw new Error(`Erro ${response.status}`);
  const dados: unknown = await response.json();
  // dados é unknown — TypeScript obriga verificação antes de usar
  // para o blog, cast com satisfação de que o servidor está correto:
  return dados as Artigo[];
}
api.ts — o problema e as abordagens disponíveis.

A abordagem completamente segura é validar os dados em runtime com uma biblioteca de validação como Zod:

ts
import { z } from "zod";

// schema descreve a forma esperada
const AutorSchema = z.object({
  id: z.number(),
  nome: z.string(),
  avatar: z.string().nullable(),
  bio: z.string().optional(),
});

const ArtigoSchema = z.object({
  id: z.number(),
  titulo: z.string(),
  descricao: z.string(),
  autor: AutorSchema,
  tags: z.array(z.string()),
  curtidas: z.number(),
  publicado: z.boolean(),
  publicadoEm: z.string().nullable(),
  slug: z.string(),
});

// inferir o tipo TypeScript do schema — single source of truth
type Artigo = z.infer<typeof ArtigoSchema>;
type Autor = z.infer<typeof AutorSchema>;

async function buscarArtigos(): Promise<Artigo[]> {
  const response = await fetch("/artigos.json");
  if (!response.ok) throw new Error(`Erro ${response.status}`);
  const dados = await response.json();
  // .parse() lança ZodError se os dados não baterem com o schema
  // TypeScript sabe que o retorno é Artigo[] se passar
  return z.array(ArtigoSchema).parse(dados);
}
Validação com Zod — tipos derivados do schema, segurança em runtime.

Para o blog com um JSON local que você controla, o cast simples é razoável. Para uma API de terceiros ou dados de formulário do usuário, Zod garante que seu código não silencia formatos inesperados.

as const e tipos literais

as const instrui o compilador a inferir os tipos mais específicos possíveis — literais em vez de tipos amplos, e readonly em vez de mutável:

ts
// sem as const: tipos amplos
const temas = ["claro", "escuro"];
// tipo: string[] — qualquer array de strings

// com as const: tipos literais e readonly
const temas = ["claro", "escuro"] as const;
// tipo: readonly ["claro", "escuro"] — exatamente esses dois valores, nessa ordem

// objetos
const config = {
  apiUrl: "/artigos.json",
  timeout: 5000,
};
// tipo: { apiUrl: string; timeout: number } — valores podem mudar

const config = {
  apiUrl: "/artigos.json",
  timeout: 5000,
} as const;
// tipo: { readonly apiUrl: "/artigos.json"; readonly timeout: 5000 }
// os valores são literais — apiUrl só pode ser "/artigos.json"

// extrair o tipo das rotas de um objeto de configuração
const ROTAS = {
  home: "/",
  artigos: "/artigos",
  sobre: "/sobre",
} as const;

type NomeRota = keyof typeof ROTAS;        // "home" | "artigos" | "sobre"
type CaminhoRota = typeof ROTAS[NomeRota]; // "/" | "/artigos" | "/sobre"

function navegar(rota: NomeRota): void {
  window.location.href = ROTAS[rota];
}
as const — inferência literal vs. inferência ampla.

as const é especialmente útil como alternativa a enums — cria o conjunto de valores com zero custo em runtime e tipos literais precisos.

Evitando any

any é um buraco no sistema de tipos — valores com tipo any podem ser usados como qualquer coisa sem erro de compilação. As alternativas:

ts
// em vez de any para dados desconhecidos: unknown
function processarDados(dados: unknown): string {
  // TypeScript obriga verificar antes de usar
  if (typeof dados === "string") return dados.toUpperCase();
  if (typeof dados === "number") return dados.toString();
  return JSON.stringify(dados);
}

// em vez de any para erros no catch: unknown (padrão com strict)
try {
  await buscarArtigos();
} catch (erro) {
  // erro tem tipo unknown com strict — mais seguro que any
  if (erro instanceof Error) {
    console.error(erro.message);
  } else {
    console.error(String(erro));
  }
}

// em vez de any para callbacks genéricos: generics
function transformar<T, U>(array: T[], fn: (item: T) => U): U[] {
  return array.map(fn);
}

// quando você realmente precisa ignorar um erro: @ts-expect-error
function funcaoLegada(x: any) {
  // @ts-expect-error — biblioteca externa retorna any, cast necessário aqui
  return (x as string).toUpperCase();
}
Substituições para any — cada situação tem uma alternativa melhor.

A regra de ouro: se você sente vontade de escrever any, pergunte “o que eu realmente sei sobre esse valor?” — a resposta quase sempre leva a unknown, um generic, ou um tipo mais específico.

Declarações ambiente (.d.ts)

Arquivos .d.ts descrevem tipos de código que não é TypeScript. Eles permitem que TypeScript entenda bibliotecas JavaScript, módulos de assets e APIs globais:

ts
// declarar um módulo de imagem — para importar .svg, .png sem erro
// src/types/assets.d.ts
declare module "*.svg" {
  const content: string;
  export default content;
}

declare module "*.png" {
  const content: string;
  export default content;
}

// declarar uma variável global — definida fora do TypeScript
// src/types/globals.d.ts
declare const __VERSION__: string; // injetada pelo bundler
declare const __DEV__: boolean;

// declarar um módulo JSON com tipo específico
// (alternativa: usar import assertions)
declare module "*.json" {
  const value: unknown;
  export default value;
}

// estender tipos de uma biblioteca — adicionar propriedades ao Window
interface Window {
  dataLayer: unknown[]; // Google Tag Manager
  __tema: "claro" | "escuro"; // variável global do blog
}
Declarações ambiente — dar tipos ao que TS não entende.

Quando uma biblioteca JavaScript não tem tipos (não vem com .d.ts e não tem @types/nome-da-lib no npm), você pode criar um .d.ts mínimo para ela:

ts
// src/types/lib-sem-tipos.d.ts
declare module "lib-sem-tipos" {
  export function funcaoX(param: string): number;
  export const versao: string;
}
Declaração mínima para biblioteca sem tipos.

Para bibliotecas populares, o DefinitelyTyped tem tipos prontos — instale com npm install -D @types/nome-da-lib.

Módulos TypeScript no blog — o resultado final

Com tudo isso, os módulos do blog tipados:

ts
export interface Autor {
  id: number;
  nome: string;
  avatar: string | null;
  bio?: string;
}

export interface Artigo {
  id: number;
  titulo: string;
  descricao: string;
  autor: Autor;
  tags: string[];
  curtidas: number;
  publicado: boolean;
  publicadoEm: string | null;
  readonly slug: string;
}

export type Tema = "claro" | "escuro";
types.ts — fonte única de verdade para os tipos do blog.
ts
export function formatarData(dateString: string): string {
  return new Intl.DateTimeFormat("pt-BR").format(new Date(dateString));
}

export function calcularTempoLeitura(palavras: number): number {
  return Math.ceil(palavras / 238);
}

export function truncar(texto: string, max: number = 160): string {
  if (texto.length <= max) return texto;
  return texto.slice(0, max).trimEnd() + "…";
}

export function gerarSlug(titulo: string): string {
  return titulo.toLowerCase().trim().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
}
utils.ts — funções puras, totalmente tipadas.
ts
import type { Artigo } from "./types.ts";

export async function buscarArtigos(): Promise<Artigo[]> {
  const response = await fetch("/artigos.json");
  if (!response.ok) throw new Error(`Erro ${response.status}`);
  return response.json() as Promise<Artigo[]>;
}

export async function enviarContato(dados: {
  nome: string;
  email: string;
  mensagem: string;
}): Promise<void> {
  const response = await fetch("/contato", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(dados),
  });
  if (!response.ok) throw new Error(`Erro ${response.status}`);
}
api.ts — fetch tipado, erro tratado.

Resumo

  • response.json() retorna Promise<any> — TypeScript não valida dados externos. Para dados críticos, use Zod ou similar para validação em runtime.
  • as const instrui o compilador a inferir tipos literais e readonly — valores viram literais ("claro" em vez de string). Útil como alternativa a enums e para derivar tipos de configurações.
  • Prefira unknown a any para dados desconhecidos — exige verificação antes de usar, mantendo a proteção do compilador.
  • Arquivos .d.ts declaram tipos para código JavaScript existente — módulos de assets (.svg, .png), variáveis globais injetadas pelo bundler, bibliotecas sem tipos.
  • Para bibliotecas com tipos: @types/nome-da-lib via npm. Para bibliotecas sem tipos: crie um .d.ts mínimo com as partes que você usa.
/ checkpoint verifique seu entendimento
questão 1 de 4

Por que TypeScript não detecta quando uma API retorna dados com formato diferente do esperado?