Menu

typeof em TypeScript: type guards e o operador de tipo typeof

O typeof tem duas funções no TypeScript. No código, é o operador do JavaScript que retorna "string", "number", "object" e assim por diante em tempo de execução, e o TypeScript faz narrowing com ele. Em um tipo, é a type query que copia o tipo de uma variável, como em keyof typeof obj e ReturnType<typeof fn>.

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

O typeof significa duas coisas no TypeScript. Em uma expressão, é o operador do JavaScript: roda, retorna uma string como "string" ou "number", e o TypeScript faz o narrowing da variável quando você compara essa string. Em uma posição de tipo, é uma type query: typeof config dá o tipo estático da variável config.

A última linha mostra a diferença: typeof custom dentro de um console.log é o operador de tempo de execução e imprime object, enquanto typeof defaults depois de type Options = é a query de tempo de compilação e nunca chega ao JavaScript.

Os dois significados em resumo

typeof de tempo de execução (JavaScript)typeof como type query (TypeScript)
Onde apareceEm qualquer expressão: if, return, console.log(...)Em posições de tipo: depois de :, em type X = ..., dentro de <...>
Exemplotypeof x === "string"let y: typeof x;
AvaliadoQuando o código rodaPelo compilador, e depois apagado
ResultadoUma de oito stringsUm tipo
Serve paraVerificar um valor e estreitar o tipo deleDerivar um tipo a partir de um valor

O TypeScript os distingue pela posição, então a mesma palavra-chave nunca significa as duas coisas ao mesmo tempo.

Resultados do typeof em tempo de execução

O operador de tempo de execução retorna uma de oito strings. Rode isto para ver o que valores comuns dão:

ValorResultado do typeofObservação
"hi", template strings"string"
42, 3.14, NaN, Infinity"number"NaN é um número; verifique com Number.isNaN
10n"bigint"
true, false"boolean"
undefined"undefined"
Symbol("id")"symbol"
funções, arrow functions, classes"function"uma classe é uma função construtora
null"object"um bug histórico do JavaScript
{}, [], new Date(), new Map()"object"arrays são objetos: use Array.isArray
new String("x"), new Number(1)"object"objetos wrapper, evite-os

Em JavaScript puro, typeof em uma variável que nunca foi declarada retorna "undefined" em vez de lançar erro. No TypeScript esse código não compila: Cannot find name 'notDeclared'. (TS2304).

Narrowing com typeof

Uma comparação com typeof em um if, um switch, um operador ternário ou depois de && estreita a variável em cada ramo. É o jeito padrão de lidar com uma union de primitivos, e a primeira verificação a usar em um unknown.

typeof x !== "string" estreita no sentido contrário, para tudo exceto string. E o compilador conhece as oito strings possíveis, então um erro de digitação na comparação é erro de compilação, e não uma verificação que é sempre falsa em silêncio:

O compilador mostra index.ts(3,7): error TS2367: This comparison appears to be unintentional because the types '"bigint" | "boolean" | "function" | "number" | "object" | "string" | "symbol" | "undefined"' and '"strnig"' have no overlap. Em JavaScript puro, o erro de digitação mandaria em silêncio toda string pelo caminho do number. Corrija a grafia e ele imprime text.

A armadilha do typeof null

Como typeof null é "object", o TypeScript estreita typeof x === "object" para object | null, e não para object. Adicione uma verificação de null, senão ler uma propriedade é erro (TS18047 'x' is possibly 'null'.).

O typeof não distingue arrays, datas, maps e objetos simples: todos são "object". Use Array.isArray(x) para arrays e x instanceof Date para instâncias de classes. Nem o typeof nem nada mais consegue verificar em tempo de execução se um valor corresponde a uma interface, porque as interfaces são apagadas; para isso, escreva uma função type guard que verifica as propriedades.

typeof em posições de tipo

Em um tipo, typeof someVariable copia o tipo que o TypeScript inferiu (ou que você declarou) para aquela variável. Isso poupa escrever à mão um tipo para um valor que já existe e mantém os dois sincronizados.

const theme = {
  primary: "#3178c6",
  spacing: 8,
  dark: false,
};

type Theme = typeof theme;
// { primary: string; spacing: number; dark: boolean }

function withSpacing(t: Theme, factor: number): Theme {
  return { ...t, spacing: t.spacing * factor };
}

console.log(withSpacing(theme, 2)); // { primary: '#3178c6', spacing: 16, dark: false }

let userName = "Ada";
const fixed = "Ada";
type A = typeof userName; // string
type B = typeof fixed;    // "Ada" (a const keeps its literal type)

O resultado depende de como a variável foi declarada: um let ou uma propriedade de objeto é alargado (string, number), um primitivo const mantém o literal type. Adicione as const a um objeto ou array e o typeof dá literal types readonly em todos os níveis. O typeof também aceita acesso a propriedades, typeof theme.spacing é number, mas não expressões arbitrárias: type T = typeof getTheme(); é um erro de sintaxe (';' expected., TS1005). Para o tipo que uma chamada retorna, use ReturnType<typeof getTheme>.

keyof typeof: as chaves de um objeto como tipo

keyof typeof obj é a combinação mais comum. O typeof transforma o objeto em um tipo, o keyof pega as chaves dele, e você obtém uma union dos nomes das propriedades que continua correta quando o objeto muda.

O mesmo truque em um array constante dá uma union dos valores dele: com const roles = ["admin", "editor"] as const, o tipo (typeof roles)[number] é "admin" | "editor". Sem as const seria só string. A página sobre keyof trata do keyof sozinho.

ReturnType<typeof fn> e typeof em classes

Utility types como ReturnType e Parameters recebem um tipo de função. O nome de uma função é um valor, então passe-o antes pelo typeof:

Escrever ReturnType<createUser> falha com 'createUser' refers to a value, but is being used as a type here. Did you mean 'typeof createUser'? (TS2749), que é o compilador pedindo exatamente isso.

Classes são o único caso em que um nome é ao mesmo tempo valor e tipo. User como tipo significa uma instância da classe; typeof User significa a própria classe, o construtor, incluindo os membros estáticos:

class Point {
  static origin = new Point(0, 0);
  constructor(public x: number, public y: number) {}
}

const p: Point = new Point(1, 2);        // an instance
const Ctor: typeof Point = Point;        // the class (constructor + statics)
const q = new Ctor(3, 4);                // q: Point
type Instance = InstanceType<typeof Point>; // Point

typeof vs instanceof vs type guard

VerificaçãoFunciona comExemplo
typeofprimitivos, funções, "é um objeto?"typeof x === "number"
Array.isArrayarraysArray.isArray(x)
instanceofinstâncias de classes (Date, Error, suas classes)x instanceof Date
inunions de objetos, por propriedade"email" in x
Função type guardinterfaces, type aliases, qualquer outra coisaisUser(x) com x is User

Escolha a ferramenta mais específica que servir. Para instâncias de classes, veja instanceof; para formatos descritos por uma interface, uma função type guard é a única verificação de tempo de execução disponível.

Perguntas frequentes

O que o typeof faz no TypeScript?

Duas coisas diferentes, dependendo de onde aparece. Em uma expressão, typeof x é o operador do JavaScript: ele roda e retorna uma string como "string" ou "object", e o TypeScript faz o narrowing de x quando você compara essa string. Em uma anotação de tipo, typeof x é uma type query: é avaliada pelo compilador, dá o tipo estático da variável x e desaparece do JavaScript gerado.

O que é keyof typeof no TypeScript?

keyof typeof obj dá a union dos nomes das propriedades de um objeto como literal types de string. typeof obj transforma o valor do objeto no tipo dele, e keyof pega as chaves desse tipo. Para const colors = { red: "#f00", blue: "#00f" }, keyof typeof colors é "red" | "blue".

Por que typeof null retorna "object"?

É um bug da primeira versão do JavaScript que não pode mais ser corrigido sem quebrar a web. O TypeScript o modela: depois de typeof x === "object", x é estreitado para object | null, então você também precisa verificar x !== null antes de ler propriedades.

Como verificar se um valor é um array no TypeScript?

Use Array.isArray(value), que estreita para um tipo array. O typeof não consegue: typeof [] é "object", igual a objetos simples e a null.

Qual é a diferença entre typeof e instanceof no TypeScript?

O typeof verifica a categoria primitiva de um valor ("string", "number", "function", "object"...). O instanceof verifica se um objeto foi criado por uma classe ou construtor específico, como Date ou a sua própria class User. Use typeof para primitivos e instanceof para instâncias de classes; nenhum dos dois consegue verificar uma interface ou um type alias, que não existem em tempo de execução.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR