← TypeScript: tipos, contratos e diagnóstico
02 / 10 · 55 MIN

Uniões, refinamento de tipos e modelos de estado

Segue os caminhos que refinam uma união e representa estados com campos obrigatórios, distinguindo exaustividade estática de validação externa.

O tipo possível muda ao longo do caminho

Uma união indica alternativas, não a soma de todas as propriedades disponíveis. Para usar uma operação, o verificador precisa de saber que ela existe em cada possibilidade ainda presente naquele ponto. typeof value === "string" pode excluir number e justificar uma chamada de toUpperCase no ramo correspondente.

Esse conhecimento não é um carimbo permanente na variável. Uma atribuição posterior pode mudar o tipo observado, desde que o novo valor seja compatível com o contrato declarado. Ao ler um excerto, marca a condição que foi tomada, as saídas antecipadas e as atribuições seguintes. O método permitido depende da situação atual do fluxo.

Saídas antecipadas e ausência

Uma função que devolve imediatamente quando x é null deixa apenas os outros membros da união nos caminhos que continuam. A saída não transforma null numa string; impede que esse caso chegue à chamada seguinte. Esta distinção ajuda a interpretar funções com vários motivos de rejeição.

O JavaScript mantém particularidades que o modelo de tipos respeita. typeof null é "object", pelo que esse teste não basta para distinguir um array de null. Uma condição de veracidade exclui null, mas também exclui uma string vazia ou zero. A guarda deve refletir os valores que o contrato aceita, em vez de procurar apenas uma forma curta de remover um aviso.

Ligar variantes aos dados que exigem

Um único objeto com status: "ok" | "error" e campos opcionais value e message admite combinações incompletas. Testar status não torna automaticamente value obrigatório. O verificador não deduz relações de negócio que o tipo não declarou.

Uma união de objetos separados pode dizer que o estado ok tem value obrigatório e o estado error tem message obrigatório. O discriminante comum, com literais distintos, seleciona a forma relevante. Isso melhora a leitura dos consumidores e expõe construções internas incompletas. Continua a ser uma representação estática: uma resposta de rede precisa de validação antes de receber este contrato.

Exaustividade revela decisões por tomar

Depois de tratar todos os membros de uma união discriminada, o caminho restante pode ter tipo never. Atribuir a variável a uma constante explicitamente never exige que nada previsto no modelo tenha ficado por tratar. Se uma extensão acrescentar pending sem alterar o switch, o novo membro torna-se visível como erro de atribuição.

Substituir a atribuição por as never muda a função da linha: passa a afirmar a impossibilidade em vez de a exigir. Um default que devolve sucesso também pode ocultar estados novos. O objetivo é obrigar a uma decisão explícita sobre cada variante, sem confundir essa verificação com um validador dos dados que chegam de fora.

Predicados são contratos a rever

Uma função com retorno x is string informa o verificador de como refinar x quando o resultado é true. A assinatura pode ser útil para centralizar verificações repetidas. Contudo, a declaração não é uma demonstração lógica de que o corpo implementa todas as condições necessárias.

Um predicado que devolve sempre true pode convencer o consumidor a chamar um método de string sobre um número. A revisão deve comparar o tipo prometido com cada caminho de aceitação e de rejeição. Também deve perguntar se a função prova apenas uma propriedade superficial ou todo o contrato anunciado. Não se atribui confiança a dados externos só porque a assinatura usa a sintaxe de predicado.

Leitura guiada de uma decisão de entrega

O exemplo descreve três estados já validados na fronteira: accepted inclui receipt, rejected inclui reason e pending inclui requestId. Cada ramo lê apenas o campo garantido pela variante. Não é preciso usar ! porque o modelo representa a relação entre estado e dados. A função só produz texto; não executa uma entrega nem altera uma decisão remota.

Imagina agora uma variante cancelled com um motivo. Identifica que linha denuncia a extensão incompleta e que ramo precisa de ser acrescentado. Se um chamador não tipado enviar um estado desconhecido, o throw do default pode atuar como defesa, mas não confirma que os campos das variantes conhecidas tenham sido validados corretamente.

Escolher a correção que conserva informação

Quando surge um diagnóstico, compara as alternativas pelo contrato que deixam ao consumidor. Acrescentar uma guarda pode preservar um valor válido; redesenhar uma união pode tornar uma relação explícita; tratar um estado novo pode evitar uma conclusão incorreta. any, ! e assertions podem remover o aviso sem fornecer nenhuma dessas propriedades.

Nos casos desta aula, os dados do briefing são factos e restrições, e a resposta exige escolher uma revisão justificada. Distingue uma falha do modelo de uma falha de validação externa. Um modelo mais preciso melhora a análise estática, mas não comprova por si só a correção de todos os produtores nem o resultado operacional de uma ação.

// TypeScript 7.0; strict: true; received state already validated.
type Delivery =
  | { status: "accepted"; receipt: string }
  | { status: "rejected"; reason: string }
  | { status: "pending"; requestId: string };

function describe(result: Delivery): string {
  switch (result.status) {
    case "accepted": return "Receipt: " + result.receipt;
    case "rejected": return "Rejected: " + result.reason;
    case "pending": return "Pending: " + result.requestId;
    default: {
      const remaining: never = result;
      throw new Error("Unexpected delivery state");
    }
  }
}
NA PRÁTICA

Segue cada ramo do switch. O modelo liga estado a campos obrigatórios. A variável remaining serve de controlo estático; o throw é uma defesa separada em execução e não substitui validação à entrada. Não é necessário executar o excerto.

Armadilhas comuns

Confundir campos opcionais com variantes; esquecer null depois de typeof object; confiar num predicado sem verificar o corpo; substituir a atribuição a never por uma assertion.

Tópicos relacionados: Tipos estáticos e valores em execução · Funções e contratos de chamada · Dados externos e fronteiras de erro

Leva esta ideia contigo

O refinamento acompanha evidência e caminhos. Um bom modelo torna explícitas as relações que os consumidores precisam de usar.

Criar conta

Referência: TypeScript Handbook: Narrowing · TypeScript 7.0; explicit strict and host configuration