Menu

Type guard em TypeScript: verificar o tipo de um objeto

Um type guard é uma verificação em tempo de execução que o TypeScript entende. Veja os type guards nativos, como escrever os seus com um type predicate value is Type, como verificar se um objeto é de um tipo, assertion functions com asserts e como validar dados unknown.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Um type guard é uma verificação em tempo de execução que o TypeScript entende, então ele estreita o tipo dentro do ramo verificado. typeof, instanceof e in são guards nativos; para todo o resto, você escreve uma função cujo tipo de retorno é um type predicate, value is Type.

isUser retorna um boolean comum em tempo de execução. O tipo de retorno value is User diz ao compilador o que um resultado true prova, e então todo if (isUser(x)) estreita x para User.

Type guards nativos

Estas verificações fazem narrowing sem nenhuma função auxiliar:

GuardExemploUse para
typeoftypeof x === "number"primitivos e funções
instanceofx instanceof Dateinstâncias de classes
in"email" in xunions de objetos, propriedades de objetos unknown
Array.isArrayArray.isArray(x)arrays
Igualdadex === null, x.kind === "circle"null/undefined, tags literais
Truthinessif (x)remover null e undefined

Todas rodam como JavaScript comum. O que o TypeScript acrescenta é o narrowing: ele lê a verificação e ajusta o tipo em cada ramo. A lista completa de formas está na página sobre type narrowing. Um guard próprio serve para verificações que não cabem em uma expressão, ou que você quer reutilizar.

Escrevendo um type predicate

Um type predicate tem a forma parameterName is Type e substitui boolean como tipo de retorno. O narrowing funciona nos dois sentidos: true estreita para Type, e false remove Type de uma union.

Passar um guard para o filter dá um array com o tipo certo. Desde o TypeScript 5.5, o compilador também infere um predicate a partir de arrow functions simples, então pets.filter((p) => p.kind === "cat") retorna Cat[] sem um guard com nome.

O tipo do predicate precisa caber no tipo do parâmetro: function f(x: string): x is number gera o erro TS2677, A type predicate's type must be assignable to its parameter's type.

O compilador confia no seu guard

O TypeScript verifica que um guard retorna um boolean. Ele não verifica se o boolean está certo. Um guard que retorna true para os valores errados faz os tipos mentirem, e o programa falha em tempo de execução sem nenhum erro de compilação.

data.price.toFixed(2) lança TypeError: Cannot read properties of undefined (reading 'toFixed') em tempo de execução. O compilador aceitou data.price como number porque o guard disse isso. Verifique toda propriedade da qual o resto do código depende, e mantenha os guards pequenos, testados e perto do tipo que descrevem.

Verificando se um objeto é de um tipo

Essa é a pergunta por trás da maioria dos guards próprios: os dados chegam como unknown (de JSON.parse, fetch, localStorage, uma mensagem) e você precisa saber se eles correspondem à sua interface. A receita:

  1. typeof value === "object" && value !== null (um objeto, e não null).
  2. "prop" in value para cada propriedade obrigatória. Em um unknown, o in acrescenta a propriedade ao tipo como unknown.
  3. typeof value.prop === "..." (ou um guard aninhado) para o tipo de cada propriedade.
  4. Array.isArray(value.items) && value.items.every(isItem) para arrays.

Para formatos grandes ou muito aninhados, escrever isso à mão fica cansativo. Bibliotecas de schema como Zod ou Valibot permitem descrever o formato uma vez e dão a você a verificação em tempo de execução e o tipo TypeScript.

Assertion functions: asserts value is Type

Uma assertion function lança um erro se a verificação falha e retorna normalmente caso contrário. O tipo de retorno dela é asserts value is Type (ou asserts condition), e tudo o que vem depois da chamada fica estreitado, sem precisar de if.

Uma regra pega muita gente: uma assertion function precisa ser chamada por meio de um nome com tipo explícito. Uma arrow function const sem anotação, const check = (v: unknown): asserts v is string => {...}, gera o erro TS2775 no local da chamada, Assertions require every name in the call target to be declared with an explicit type annotation. Use uma declaração function, ou anote a constante com um tipo de função.

Guards vs assertions vs casts

FerramentaVerifica em tempo de execução?EstreitaEm caso de falha
Guard nativo (typeof, in...)Simdentro do ramovai para o outro ramo
Função value is TSim (seu código)dentro do ramovai para o outro ramo
Função asserts value is TSim (seu código)depois da chamadalança um erro
value as TNãoa expressãonada: o tipo errado se espalha

Uma type assertion (as) muda o tipo sem verificar nada. Em uma fronteira em que os dados vêm de fora, um guard ou uma assertion function é a versão segura da mesma ideia.

Guards baseados em this nas classes

Um método pode estreitar o objeto em que é chamado com this is Type. Isso é prático em hierarquias de classes:

class FileNode {
  constructor(public name: string) {}
  isDirectory(): this is DirectoryNode {
    return this instanceof DirectoryNode;
  }
}

class DirectoryNode extends FileNode {
  children: FileNode[] = [];
}

function count(node: FileNode): number {
  return node.isDirectory() ? node.children.length : 0; // node: DirectoryNode in the true branch
}

Perguntas frequentes

O que é um type guard no TypeScript?

Qualquer verificação em tempo de execução que o TypeScript usa para estreitar um tipo: typeof x === "string", x instanceof Date, "id" in x, Array.isArray(x) ou a chamada de uma função cujo tipo de retorno é um type predicate como x is User. Dentro do ramo verificado, a variável tem o tipo mais estreito.

Como verificar se um objeto é de um tipo no TypeScript?

Os tipos não existem em tempo de execução, então você verifica as propriedades: escreva uma função isUser(value: unknown): value is User que testa typeof value === "object", value !== null e cada propriedade obrigatória com in e typeof. Depois de if (isUser(x)), x tem o tipo User. Para classes, x instanceof MyClass basta.

O que significa "value is Type" no TypeScript?

É um type predicate, usado como tipo de retorno de uma função. A função continua retornando um boolean em tempo de execução, mas quando retorna true o TypeScript estreita o argumento para Type no local da chamada, e quando retorna false estreita para os outros membros da union. O compilador não verifica o corpo da função, então a verificação precisa estar correta.

Qual é a diferença entre um type guard e uma assertion function?

Um type guard (x is T) retorna um boolean e faz narrowing dentro de um if. Uma assertion function (asserts x is T) não retorna nada e lança um erro quando a verificação falha, então tudo o que vem depois da chamada fica estreitado sem if. Use guards para ramificar e assertions para "isto precisa valer, senão pare".

Dá para verificar se um objeto implementa uma interface no TypeScript?

Não diretamente: as interfaces são apagadas e o instanceof não as aceita. Escreva um type guard que verifica as propriedades da interface, ou acrescente uma propriedade-tag literal (kind: "user") e compare-a. Bibliotecas de schema como o Zod geram a verificação e o tipo a partir de uma única definição.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR