Um ponto de exclamação depois de uma expressão, value!, é o operador non-null assertion. Ele remove null e undefined do tipo, então um number | undefined pode ser usado como number. É uma promessa ao compilador, não uma verificação: nada acontece em tempo de execução.
Sem o !, tea.toFixed(2) gera o erro TS18048, 'tea' is possibly 'undefined'. Com ele, o código compila porque você disse ao compilador que a chave existe.
O que x! faz em tempo de execução: nada
O ! é apagado da saída. O JavaScript compilado de prices.get("coffee")! é só prices.get("coffee"). Se a afirmação estiver errada, o erro aparece depois, no primeiro lugar em que o valor ausente é usado:
O programa imprime Cannot read properties of undefined (reading 'toFixed'). A quebra acontece na linha depois do !, e em código real pode ficar bem mais longe: o undefined pode ser guardado em um objeto e estourar em outro arquivo. Essa distância é o que torna o ! arriscado.
Definite assignment: let x!: T
O mesmo símbolo em uma declaração significa algo parecido. O TypeScript acompanha se uma variável é atribuída antes de ser lida, e não consegue seguir uma atribuição feita dentro de outra função:
index.ts(9,13): error TS2454: Variable 'config' is used before being assigned.
let config!: { port: number }; é uma definite assignment assertion: "isto será atribuído antes de qualquer leitura". Ela resolve o erro, com o mesmo porém do x!: se init() for pulado algum dia, a leitura recebe undefined. Reestruturar costuma ser melhor, por exemplo const config = init(); com init retornando o objeto.
Propriedades de classe: prop!: T
Com strictPropertyInitialization (parte do strict), toda propriedade de classe precisa ser inicializada na declaração ou no construtor. Senão você recebe o TS2564: Property 'socket' has no initializer and is not definitely assigned in the constructor. Quando uma propriedade é definida depois por um método ou por um framework, prop!: T diz ao compilador para aceitá-la:
O primeiro console.log mostra a brecha: o tipo diz que socket sempre existe, mas antes de open() ele é undefined. Chamar c.socket.send nesse momento compila e lança um erro. Se a propriedade pode mesmo faltar, declare-a como socket?: ... e verifique, ou crie o objeto no construtor. O lugar principal em que prop! é prática padrão é um framework que preenche uma propriedade depois da construção: o @ViewChild(...) child!: ChildDirective do Angular (definido antes de ngAfterViewInit rodar), ou classes de entidade de ORM cujas colunas a biblioteca preenche ao carregar uma linha (a documentação do MikroORM escreve @Property() title!: string).
Alternativas mais seguras
A maioria dos ! pode ser trocada por algo que o compilador verifica, ou por uma verificação que falha de forma clara no lugar certo:
Os dois helpers estreitam o tipo como o ! faz, mas uma suposição errada produz missing HOST na hora, em vez de um TypeError em outro lugar. assertDefined é uma assertion function (asserts value is ...): depois da chamada, o compilador trata host como string. Mais padrões em type guards.
| Em vez de | Escreva | O que acontece quando o valor falta |
|---|---|---|
user!.name | if (user) { user.name } | o bloco é pulado |
user!.name | user?.name | undefined |
count! | count ?? 0 | o padrão é usado |
map.get(k)! | must(map.get(k), "k") | um erro claro naquela linha |
let x!: T | const x = compute() | nada pode dar errado |
Os outros pontos de exclamação
O ! significa coisas diferentes dependendo de onde está:
| Código | Significado |
|---|---|
value! (depois de uma expressão) | non-null assertion, só no TypeScript |
let x!: T, prop!: T | definite assignment assertion, só no TypeScript |
!value (antes de uma expressão) | NOT lógico, JavaScript puro |
!!value | converte para boolean, JavaScript puro |
a !== b, a != b | desigualdade, JavaScript puro |
Só os dois primeiros são apagados na compilação. !value e !!value rodam em tempo de execução e retornam um boolean.
Perguntas frequentes
O que significa um ponto de exclamação depois de uma variável no TypeScript?
value! é o operador non-null assertion. Ele diz ao compilador que value não é null nem undefined, então o tipo perde esses dois membros: string | undefined vira string. Ele é removido do JavaScript gerado e não acrescenta nenhuma verificação em tempo de execução, então, se você estiver errado, o programa falha depois com um TypeError.
Qual é a diferença entre ! e ? no TypeScript?
x! afirma que o valor existe e dá a você o tipo sem null, sem verificar nada. x?.y verifica em tempo de execução: se x for null ou undefined, ele para e retorna undefined. Em uma declaração, name?: string torna uma propriedade opcional, enquanto name!: string diz que uma propriedade obrigatória será atribuída em algum lugar que o compilador não enxerga.
O que significa let x!: string?
É uma definite assignment assertion. Ela diz ao compilador que a variável será atribuída antes de ser lida, mesmo que ele não consiga provar isso (por exemplo, a atribuição acontece dentro de outra função). Sem ela, ler a variável gera o erro TS2454, Variable 'x' is used before being assigned.
Como corrigir "has no initializer and is not definitely assigned in the constructor"?
Esse é o erro TS2564 do strictPropertyInitialization. Dê um valor inicial à propriedade, atribua-a no construtor, torne-a opcional (prop?: T) ou, se um framework ou um método de inicialização realmente a define antes do uso, escreva prop!: T. O ! é a última opção porque nada verifica a promessa.
O operador non-null assertion é uma má prática?
Não é errado, mas cada ! é uma afirmação não verificada. Configurações de lint como @typescript-eslint/no-non-null-assertion o apontam. Prefira uma verificação que faça narrowing (if (x), x ?? fallback, x?.y) ou um helper que lance um erro claro. Deixe o ! para lugares em que o valor é garantido por uma lógica que o compilador não consegue acompanhar.