En Typecript, ¿cuál es el! (signo de exclamación / explosión) al desreferenciar a un miembro?

453

Al mirar el código fuente de una regla tslint, me encontré con la siguiente declaración:

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

Observe el !operador después node.parent. ¡Interesante!

Primero intenté compilar el archivo localmente con mi versión actualmente instalada de TS (1.5.3). El error resultante apuntó a la ubicación exacta de la explosión:

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

A continuación, actualicé al último TS (2.1.6), que lo compiló sin problemas. Por lo tanto, parece ser una característica de TS 2.x. Pero la transpilación ignoró por completo la explosión, lo que resultó en el siguiente JS:

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

Mi Google fu hasta ahora me ha fallado.

¿Qué es el operador de signo de exclamación de TS y cómo funciona?

Mike Chamberlain
fuente

Respuestas:

689

Ese es el operador de aserción no nulo. Es una manera de decirle al compilador "esta expresión no puede ser nullo undefinedaquí, así que no te quejes de la posibilidad de que sea nullo undefined". A veces, el verificador de tipo no puede tomar esa determinación por sí mismo.

Se explica aquí :

Se !puede usar un nuevo operador de expresión posterior a la corrección para afirmar que su operando no es nulo y no está definido en contextos donde el verificador de tipos no puede concluir ese hecho. Específicamente, la operación x!produce un valor del tipo de xcon nully undefinedexcluido. Similar a las aserciones de tipo de los formularios <T>xy x as T, el !operador de aserción no nulo simplemente se elimina en el código JavaScript emitido.

Encuentro el uso del término "afirmar" un poco engañoso en esa explicación. Es "afirmar" en el sentido de que el desarrollador lo está afirmando , no en el sentido de que se va a realizar una prueba. La última línea de hecho indica que no se emite ningún código JavaScript.

Louis
fuente
102
Un buen llamado a la ambigüedad "afirmativa".
Estus Flas
8
Buena explicación. Me parece una buena práctica hacer un console.assert()en la variable en cuestión antes de agregar un !después. Debido a que add !le dice al compilador que ignore la verificación nula, compila a noop en javascript. Entonces, si no está seguro de que la variable no sea nula, es mejor que haga una verificación explícita de afirmación.
Jayesh
12
Como ejemplo motivador: el uso del nuevo tipo de ES Map con código como dict.has(key) ? dict.get(key) : 'default';el compilador TS no puede inferir que la getllamada nunca devuelve nulo / indefinido. dict.has(key) ? dict.get(key)! : 'default';estrecha el tipo correctamente.
kitsu.eb
1
¿Hay jerga para este operador, como cómo el operador de Elvis se refiere al operador binario?
ebakunin
@ Jayesh, ¿podría expandir la buena práctica console.assert (), podría publicar un ejemplo?
Christopher Francisco
168

La respuesta de Louis es excelente, pero pensé que trataría de resumirla sucintamente:

El operador de explosión le dice al compilador que relaje temporalmente la restricción "no nula" que de otro modo podría exigir. Le dice al compilador: "Como desarrollador, sé mejor que tú que esta variable no puede ser nula en este momento".

Mike Chamberlain
fuente
85
Entonces, como desarrollador, te has equivocado.
Mike Chamberlain
99
O, como compilador, se ha estropeado. Si el constructor no inicializa una propiedad pero lo hace un enlace de ciclo de vida y el compilador no lo reconoce.
Mukus
26
Esto no es responsabilidad del compilador de TS. A diferencia de otros lenguajes (por ejemplo, C #), JS (y, por lo tanto, TS) no exige que las variables se inicialicen antes de su uso. O, para verlo de otra manera, en JS todas las variables declaradas con varo letestán implícitamente inicializadas en undefined. Además, las propiedades de instancia de clase se pueden declarar como tales, por lo que class C { constructor() { this.myVar = undefined; } }es perfectamente legal. Finalmente, los ganchos del ciclo de vida dependen del marco; por ejemplo, Angular y React los implementan de manera diferente. Por lo tanto, no se puede esperar que el compilador de TS razone sobre ellos.
Mike Chamberlain
1
¿Existe un caso de uso válido para el operador de bang considerando la asombrosidad del análisis de tipo basado en el flujo de control en TS?
Eugene Karataev
1
@EugeneKarataev Sí, los marcos a menudo inicializan variables dentro de sí mismos y el análisis de flujo de control ts no puede detectarlo. Su uso ciertamente se reduce, pero se encontrará con casos en los que lo necesite.
arg20