Diferença entre '!' e '?' em TypeScript

Aug 21 2020
Class Employee {
  firstName: string;
  lastName!: string;
  middleName?: string;
}

Qual é a diferença nesses 3 diferentes campos de Employeeaula?

Exemplo ao Vivo

Respostas

3 T.J.Crowder Aug 21 2020 at 19:39

O ?nessa posição marca a propriedade opcional .

O !nessa posição é a afirmação de atribuição definitiva . É uma espécie de versão em nível de declaração do operador de asserção não nulo , mas usado em uma propriedade (também pode ser usado em variáveis) em vez de em uma expressão.

Existem dois - ou possivelmente três - erros nesse exemplo:

  1. Classdeve ser class; JavaScript e TypeScript diferenciam maiúsculas de minúsculas.

  2. Você precisa de um inicializador firstName(ou um construtor que atribua a ele incondicionalmente).

  3. O !on lastNamediz ao TypeScript que lastNamedefinitivamente será atribuído, suprimindo o tipo de erro que você está obtendo firstName, mas nada (no exemplo) realmente faz a atribuição que usar !lá promete TypeScript que você tem certeza de que está fazendo.

Editar: O código que você vinculou posteriormente trata dos itens 1 e 2 acima, mas não 3. O TypeScript não avisará que lastNamenunca é atribuído e assume que seu valor é uma string, quando na verdade ela não está lá e, portanto, a leitura de seu valor resultará em undefined.

1 axiac Aug 21 2020 at 19:24

Eles estão bem escondidos na documentação do TypeScript .

?é descrito em interfaces , ele marca uma propriedade opcional .

!é o operador de asserção definitivo . Diz ao compilador que a propriedade está definida (não nullou undefined) mesmo se as análises do TypeScript não puderem detectar isso.


Btw, Classnão é uma palavra-chave TypeScript ou JavaScript e produz um erro nessa posição. A palavra-chave para declarar uma classe é class. Identificadores e palavras-chave TypeScript e JavaScript diferenciam maiúsculas de minúsculas ( Classe classsão coisas diferentes).

1 SherifElmetainy Aug 23 2020 at 23:12

Ao ter a opção de compilador strictNullChecks: false

Se você tiver strictNullChecks: falseem seu tsconfig.json, eles são exatamente os mesmos, pois ter strictNullChecksdesabilitado significa que todos os campos são nulos ou indefinidos como valores válidos.

Ao ter a opção de compilador strictNullChecks: true

class Employee {
   firstName: string;
   lastName!: string;
   middleName?: string;
}

firstName: stringsignifica que firstNamedeve ser um string. nullou undefinednão são valores válidos para firstName.

Todos os campos terão um valor padrão, undefinedo que resultará em um erroProperty 'firstName' has no initializer and is not definitely assigned in constructor

Para silenciar o erro, você precisa alterar a declaração firstName: string = 'Some default value'ou adicionar um construtor e atribuir um valor para ele no construtor.

constructor() {
    this.firstName = 'some default value';
}

Agora, para o! sintaxe. A lastName!: stringsintaxe é semelhante a porque lastName: stringbasicamente diz que only stringé o único tipo permitido. nulle undefinednão são permitidos. Mas isso silenciará o compilador sobre o erro de atribuição definitivo. Digamos que você tenha o seguinte código.

class Employee {firstName: string; lastName !: string; middleName ?: string;

  constructor() {
      // This will silence the compiler about first name not initialized
      this.firstName = 'some default value';
      // The compiler cannot tell that lastName is assigned in init() function
      this.init();
  }
  
  private init(): void {
      this.lastName = 'some default value';
  }
}

No código anterior, lastNameé definitivamente atribuído na constructorvia da this.init()chamada. No entanto, o compilador não pode saber disso. Então, adicionando o! basicamente diz ao compilador "cale a boca, eu sei o que estou fazendo" . Cabe a você garantir a correção do seu código.

Sobre a middleName?: stringsintaxe. Isso é exatamente o mesmo que middleName: string | undefined; Como todos os valores têm o valor padrão de undefined, o compilador não reclamará que middleNamenão foi atribuído.