Menu

null e undefined em TypeScript: verificações, ?. e ??

Com strictNullChecks, null e undefined são tipos separados que o TypeScript obriga você a tratar. Veja como verificá-los, optional chaining (?.), o operador de dupla interrogação (??) e ??=, e a diferença entre uma propriedade opcional e | undefined.

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

Com strictNullChecks ligado (ele faz parte do strict), null e undefined são tipos próprios. Uma string nunca pode ser null; um valor que pode faltar precisa dizer isso no tipo, como string | null, e o TypeScript obriga você a tratar esse caso antes de usar o valor.

Depois da verificação, o compilador sabe que name é uma string, então .split é permitido. O resto desta página mostra os jeitos de fazer essa verificação e os operadores que a encurtam.

strictNullChecks e os erros "possibly null"

Quando um tipo inclui null ou undefined, o TypeScript se recusa a usar o valor como se ele sempre existisse:

index.ts(3,22): error TS18047: 'name' is possibly 'null'.

A versão com undefined é a TS18048, 'x' is possibly 'undefined'. Sem strictNullChecks, null e undefined são aceitos em todo tipo e esse código compila, e depois quebra em tempo de execução com um TypeError na primeira vez em que name for null. Essa classe de bug é o principal motivo para manter o strict ligado.

De onde esses tipos vêm no código do dia a dia:

OrigemTipo
arr.find(...)T | undefined
map.get(key)V | undefined
propriedade opcional p?: TT | undefined ao ler
parâmetro opcional x?: TT | undefined dentro da função
str.match(re)RegExpMatchArray | null
JSON.parse(text)any, então nada é verificado

Verificando null e undefined

Todas as verificações abaixo estreitam o tipo dentro do bloco. Escolha a que corresponde ao que você quer excluir.

VerificaçãoRemove do tipo
x !== undefinedundefined
x !== nullnull
x != nullnull e undefined
typeof x !== "undefined"undefined
if (x)null e undefined, e também descarta os valores 0, "", false, NaN

== null é o único lugar em que a igualdade frouxa é idiomática: é verdadeira exatamente para null e undefined, e nada mais. Uma verificação de truthiness teria tratado a string vazia como ausente, o que muitas vezes é um bug.

Optional chaining: ?.

a?.b lê b se a não for null nem undefined; caso contrário, para e retorna undefined. O mesmo operador funciona com índices, a?.[i], e com chamadas, fn?.().

O tipo de bob.address?.city é string | undefined: o optional chaining acrescenta undefined ao resultado, então normalmente você o combina com ??. O comportamento em tempo de execução é JavaScript puro; veja optional chaining para os detalhes do curto-circuito.

A dupla interrogação: ??

a ?? b retorna a, a menos que ele seja null ou undefined; nesse caso retorna b. Ele substitui o idioma antigo a || b, que também descarta 0, "", false e NaN:

Valor à esquerdaleft || "d"left ?? "d"
null"d""d"
undefined"d""d"
0"d"0
"""d"""
false"d"false
NaN"d"NaN

Nos tipos, ?? remove null e undefined do lado esquerdo e faz a union do resto com o lado direito, e é por isso que scores.get("Linus") ?? 0 pode ser atribuído a um number.

Atribuição nullish: ??=

a ??= b atribui b a a só quando a é null ou undefined. Os irmãos ||= e &&= atribuem quando o lado esquerdo é falsy ou truthy.

retries: 0 sobrevive ao ??=, enquanto o label vazio é substituído pelo ||=. Depois de opts.retries ??= 3, o TypeScript estreita opts.retries para number no resto da função.

Propriedades opcionais vs | undefined

nickname?: string e nickname: string | undefined se leem igual, mas diferem em se a chave precisa existir:

Use ? quando quem chama pode omitir a propriedade, e | undefined quando você quer que todos a passem explicitamente, mesmo que o valor seja undefined. A opção exactOptionalPropertyTypes (que não faz parte do strict) aperta ainda mais o ?: nickname?: string passa a rejeitar { nickname: undefined } e só aceita uma chave ausente ou uma string. Parâmetros opcionais de função (x?: number) se comportam como propriedades opcionais: dentro da função, x é number | undefined.

null ou undefined: qual usar

O TypeScript não impõe uma escolha, mas misturar os dois em uma base de código faz toda verificação ter que tratar dois casos. Uma convenção comum:

  • Use undefined (e propriedades opcionais) para "não definido" nos seus próprios tipos. É o que o JavaScript produz por padrão: propriedades ausentes, argumentos omitidos, find e Map.get sem resultado.
  • Aceite null onde uma API o entrega: JSON não tem undefined, e String.prototype.match e muitos métodos do DOM retornam null.
  • Verifique com == null quando o valor pode ser qualquer um dos dois.

A indexação de arrays é a única brecha: users[5] tem o tipo do elemento mesmo quando o índice está fora do intervalo. A opção noUncheckedIndexedAccess (que não faz parte do strict) acrescenta | undefined a todo acesso por índice, para que o compilador pegue isso também.

Perguntas frequentes

O que significa a dupla interrogação no TypeScript?

a ?? b é o operador nullish coalescing do JavaScript. Ele retorna a, a menos que a seja null ou undefined; nesse caso retorna b. Diferente de ||, ele mantém outros valores falsy como 0, "" e false. O TypeScript remove null e undefined do tipo do lado esquerdo, então quando a é string | undefined, a ?? "x" é uma string.

Como verificar se um valor é undefined no TypeScript?

Compare: if (value !== undefined) { ... }. O TypeScript estreita o tipo dentro do bloco. Para descartar null e undefined em uma verificação só, use value != null (igualdade frouxa), o único lugar em que ==/!= é idiomático. Uma verificação de truthiness (if (value)) também faz narrowing, mas descarta 0, "" e false.

O que significa "Object is possibly undefined"?

Os erros TS18048 ('x' is possibly 'undefined') e TS18047 ('x' is possibly 'null'), ou TS2532 (Object is possibly 'undefined') quando o valor não tem um nome simples, como em getUser().address.city, vêm do strictNullChecks: o tipo inclui undefined ou null, e o código usa o valor como se ele não pudesse ser. Verifique antes, use optional chaining (x?.name), forneça um padrão com ?? ou mude o tipo se o valor realmente não puder faltar.

Qual é a diferença entre null e undefined no TypeScript?

São dois tipos separados, cada um com um único valor. O JavaScript usa undefined para coisas que nunca foram definidas (uma propriedade ausente, um argumento omitido, Map.get com uma chave inexistente), e as APIs usam null para um "sem valor" intencional (JSON, muitos métodos do DOM). O TypeScript os rastreia separadamente, então string | null não aceita undefined. Muitas bases de código escolhem undefined para o próprio código e só aceitam null nas fronteiras.

Uma propriedade opcional é o mesmo que | undefined?

Não exatamente. name?: string significa que a propriedade pode faltar por completo, e lê-la dá string | undefined. name: string | undefined significa que a propriedade precisa estar presente, mesmo que o valor seja undefined. Com exactOptionalPropertyTypes ligado, name?: string também deixa de aceitar um valor undefined explícito.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR