No texto datilografado, qual é o! operador (ponto de exclamação / estrondo) ao cancelar a referência de um membro?

453

Ao olhar para o código-fonte para uma regra tslint, deparei-me com a seguinte declaração:

if (node.parent!.kind === ts.SyntaxKind.ObjectLiteralExpression) {
    return;
}

Observe o !operador depois node.parent. Interessante!

Primeiro tentei compilar o arquivo localmente com a versão do TS atualmente instalada (1.5.3). O erro resultante apontou para a localização exata do estrondo:

$ tsc --noImplicitAny memberAccessRule.ts 
noPublicModifierRule.ts(57,24): error TS1005: ')' expected.

Em seguida, atualizei para o TS mais recente (2.1.6), que o compilou sem problemas. Portanto, parece ser um recurso do TS 2.x. Mas a transpilação ignorou completamente o estrondo, resultando no seguinte JS:

if (node.parent.kind === ts.SyntaxKind.ObjectLiteralExpression) {
    return;
}

Até agora, meu Google fu me falhou.

O que é o operador de ponto de exclamação da TS e como ele funciona?

Mike Chamberlain
fonte

Respostas:

688

Esse é o operador de asserção não nulo. É uma maneira de dizer ao compilador "essa expressão não pode ser nullou é undefinedaqui, então não reclame da possibilidade de ser nullou undefined". Às vezes, o verificador de tipos é incapaz de fazer essa determinação.

É explicado aqui :

Um novo !operador de expressão pós-correção pode ser usado para afirmar que seu operando é não nulo e indefinido em contextos em que o verificador de tipos não pode concluir esse fato. Especificamente, a operação x!produz um valor do tipo de xcom nulle undefinedexcluído. Semelhante às asserções de tipo dos formulários <T>xe x as T, o !operador de asserção não nula é simplesmente removido no código JavaScript emitido.

Acho o uso do termo "afirmar" um pouco enganador nessa explicação. É "afirmar" no sentido de que o desenvolvedor está afirmando , não no sentido de que um teste será executado. A última linha realmente indica que não resulta na emissão de código JavaScript.

Louis
fonte
102
Boa chamada para 'afirmar' a ambiguidade.
Estus Flask
8
Boa explicação. Eu acho uma boa prática fazer um console.assert()na variável em questão antes de anexar um !depois dela. Como o add !está dizendo ao compilador para ignorar a verificação nula, ele é compilado para noop em javascript. Portanto, se você não tiver certeza de que a variável não é nula, faça uma verificação explícita de afirmação.
Jayesh
12
Como um exemplo motivador: o uso do novo tipo de mapa ES com código como dict.has(key) ? dict.get(key) : 'default';o compilador TS não pode inferir que a getchamada nunca retorna nula / indefinida. dict.has(key) ? dict.get(key)! : 'default';restringe o tipo corretamente.
Kitsu.eb 30/05
1
Existe uma gíria para esse operador, como a maneira como o operador Elvis se refere ao operador binário?
ebakunin 9/01
@ Jayesh, você poderia expandir as boas práticas de console.assert (), poderia postar um exemplo?
Christopher Francisco
168

A resposta de Louis é ótima, mas pensei em tentar resumir sucintamente:

O operador bang instrui o compilador a relaxar temporariamente a restrição "não nula" que, de outra forma, poderia exigir. Ele diz ao compilador: "Como desenvolvedor, sei melhor que você que essa variável não pode ser nula no momento".

Mike Chamberlain
fonte
85
Então, como desenvolvedor, você estragou tudo.
Mike Chamberlain
9
Ou, como compilador, ele estragou tudo. Se o construtor não inicializar uma propriedade, mas um gancho do ciclo de vida o fizer e o compilador não reconhecer isso.
22618 Mukus
26
Isso não é de responsabilidade do compilador TS. Diferentemente de outras linguagens (por exemplo, C #), o JS (e, portanto, o TS) não exige que as variáveis ​​sejam inicializadas antes do uso. Ou, de outra forma, em JS todas as variáveis ​​declaradas com varou letsão implicitamente inicializadas undefined. Além disso, as propriedades da instância de classe podem ser declaradas como tal, portanto, class C { constructor() { this.myVar = undefined; } }é perfeitamente legal. Finalmente, os ganchos do ciclo de vida dependem da estrutura; por exemplo, Angular e React os implementam de maneira diferente. Portanto, não se pode esperar que o compilador TS raciocine sobre eles.
9268 Mike
1
Existe um caso de uso válido para o operador bang, considerando a grandiosidade da análise de tipo baseada em fluxo de controle no TS?
Eugene Karataev 22/04
1
@EugeneKarataev Sim, existem estruturas frequentemente inicializando variáveis ​​dentro de si e a análise de fluxo ts control não pode capturá-la. Seu uso é certamente reduzido, mas você encontrará as instâncias em que precisa.
arg20