¿Typecript admite el? ¿operador? (¿Y cómo se llama?)


337

¿TypeScript actualmente (o hay planes para) apoyar al operador de navegación segura de?.

es decir:

var thing = foo?.bar
// same as:
var thing = (foo) ? foo.bar : null;

Además, ¿hay un nombre más común para este operador?


3
@mattytommo tiene eso en c #, se llama operador de fusión nula y usa el ?? sintaxis weblogs.asp.net/scottgu/archive/2007/09/20/…
basarat

2
@BasaratAli Desafortunadamente no, la fusión está bien property ?? property2, pero si lo intentaras property.company ?? property1.companyy propertyfue nulo, obtendrías unNullReferenceException
mattytommo

1
@mattytommo Gracias Lo entiendo ahora '?.' en realidad absorbe todas las referencias nulas en la cadena. Dulce.
basarat

99
@mattytommo, esto sí existe ahora para C #: msdn.microsoft.com/en-us/library/dn986595.aspx
Highmastdon el

99
El representante de Microsoft que nos visitó lo llamó el operador de Elvis, ya que el signo de interrogación se parece al cabello de Elvis y al micrófono que está cantando ...
Zymotik

Respuestas:


168

Actualización : es compatible a partir de TypeScript 3.7 y se llama Encadenamiento opcional : https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#optional-chaining

No puedo encontrar ninguna referencia al respecto en la especificación del lenguaje TypeScript .

En cuanto a cómo llamar a este operador en CoffeeScript, se llama operador existencial (específicamente, la "variante de acceso" del operador existencial).

De la documentación de CoffeeScript sobre operadores :

La variante de acceso del operador existencial ?.se puede utilizar para absorber referencias nulas en una cadena de propiedades. Úselo en lugar del punto de acceso .en casos donde el valor base puede ser nulo o indefinido .

Entonces, la variante de acceso del operador existencial parece ser la forma correcta de referirse a este operador; y TypeScript actualmente no parece admitirlo (aunque otros han expresado su deseo de esta funcionalidad ).


28
"variante de acceso del operador existencial". Naturalmente. Tan pegadizo, es casi imposible de olvidar. :). Gracias por la respuesta extremadamente completa.
Marty Pitt

1
@MartyPitt ¡Claro que sí! Estoy de acuerdo, me encantaría ver a) una adopción más amplia de un operador como este (¡C #, por favor!) Yb) un nombre mejor (el operador de "navegación segura" de su publicación de blog vinculada tiene un buen sonido).
Donut

2
Angular implementa esto en sus plantillas: angular.io/guide/…
Enzoaeneas

55
En otros idiomas se llama el operador "Elvis"
k0enf0rNL

44
Se anunció para TypeScript 3.7.0 ( github.com/microsoft/TypeScript/issues/… )
c_froehlich

146

¿No es tan bueno como un single ?, pero funciona:

var thing = foo && foo.bar || null;

Puede usar tantos && como desee:

var thing = foo && foo.bar && foo.bar.check && foo.bar.check.x || null;

33
&& evalúa siempre que la afirmación sea verdadera. Si es cierto, devuelve el último valor. Si es falso, devuelve el primer valor que se evaluó como falso. Puede ser 0, nulo, falso, etc. || devuelve el primer valor que se evalúa como verdadero.
A. KR

34
No funciona bien si la barra está definida pero se evalúa como falsa (como booleano falso o cero).
mt_serg

96

Esto se define en la especificación de encadenamiento opcional ECMAScript, por lo que probablemente deberíamos referirnos al encadenamiento opcional cuando discutimos esto. Implementación probable:

const result = a?.b?.c;

En resumidas cuentas, el equipo de TypeScript está esperando que la especificación ECMAScript se ajuste, por lo que su implementación puede ser ininterrumpida en el futuro. Si implementaran algo ahora, terminaría necesitando cambios importantes si ECMAScript redefine su especificación.

Ver especificación de encadenamiento opcional

Cuando algo nunca va a ser JavaScript estándar, el equipo de TypeScript puede implementarlo como mejor les parezca, pero para futuras adiciones de ECMAScript, desean preservar la semántica incluso si otorgan acceso temprano, como lo han hecho para muchas otras características.

Cortes Cortos

Por lo tanto, todos los operadores funky de JavaScripts están disponibles, incluidas las conversiones de tipo como ...

var n: number = +myString; // convert to number
var b: bool = !!myString; // convert to bool

Solución manual

Pero volviendo a la pregunta. Tengo un ejemplo obtuso de cómo puedes hacer algo similar en JavaScript (y, por lo tanto, TypeScript), aunque definitivamente no estoy sugiriendo que sea una función elegante como la que realmente buscas.

(foo||{}).bar;

Así que si fooes undefinedel resultado es undefinedy si fooestá definido y tiene una propiedad llamada barque tiene un valor, el resultado es ese valor.

Pongo un ejemplo en JSFiddle .

Esto se ve bastante incompleto para ejemplos más largos.

var postCode = ((person||{}).address||{}).postcode;

Función de cadena

Si está desesperado por una versión más corta mientras la especificación aún está en el aire, en algunos casos utilizo este método. Evalúa la expresión y devuelve un valor predeterminado si la cadena no puede satisfacerse o termina siendo nula / indefinida (tenga en !=cuenta que aquí es importante, no queremos usarla !==ya que queremos un poco de malabares positivos aquí).

function chain<T>(exp: () => T, d: T) {
    try {
        let val = exp();
        if (val != null) {
            return val;
        }
    } catch { }
    return d;
}

let obj1: { a?: { b?: string }} = {
    a: {
        b: 'c'
    }
};

// 'c'
console.log(chain(() => obj1.a.b, 'Nothing'));

obj1 = {
    a: {}
};

// 'Nothing'
console.log(chain(() => obj1.a.b, 'Nothing'));

obj1 = {};

// 'Nothing'
console.log(chain(() => obj1.a.b, 'Nothing'));

obj1 = null;

// 'Nothing'
console.log(chain(() => obj1.a.b, 'Nothing'));

1
interesante pero en mi caso (this.loop || {}).nativeElementdiciendo Property 'nativeElement' does not exist on type '{}'. any this.looptypeof angular.io/api/core/ElementRef
kuncevic.dev

@Kuncevic: debe ... 1) proporcionar un valor predeterminado compatible en lugar de {}, o 2) usar una aserción de tipo para silenciar el compilador.
Fenton

Asumir fooes un objeto útil real: (foo || {}).bargeneralmente no se compilará en mecanografiado porque {}no será del mismo tipo que foo. Ese es el problema que la solución de @ VeganHunter pretende evitar.
Simon_Weaver

1
@Simon_Weaver entonces (foo || {bar}). Bar permitirá que el compilador funcione sin problemas y creo que la verbosidad es aceptable.
Harps

@harps en realidad esto solo se compila si la barra se define como una variable, lo que probablemente no sería
Simon_Weaver

83

Actualización: ¡Sí, es compatible ahora!

Acaba de ser lanzado con TypeScript 3.7: https://devblogs.microsoft.com/typescript/announcing-typescript-3-7/

Se llama encadenamiento opcional : https://devblogs.microsoft.com/typescript/announcing-typescript-3-7/#optional-chaining

Con ella lo siguiente:

let x = foo?.bar.baz(); 

es equivalente a:

let x = (foo === null || foo === undefined) ?
    undefined :
    foo.bar.baz();

Vieja respuesta

Hay una solicitud de función abierta para esto en github donde puede expresar su opinión / deseo: https://github.com/Microsoft/TypeScript/issues/16


36

¡Edite el 13 de noviembre de 2019!

A partir del 5 de noviembre de 2019, se envió TypeScript 3.7 y ahora es compatible con ?. el operador de encadenamiento opcional 🎉🎉🍾🍾🎉 !!!

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#optional-chaining


Solo para fines históricos:

Editar: He actualizado la respuesta gracias al comentario fracz.

TypeScript 2.0 lanzado !.No es lo mismo que ?.(Safe Navigator en C #)

Vea esta respuesta para más detalles:

https://stackoverflow.com/a/38875179/1057052

Esto solo le dirá al compilador que el valor no es nulo o no está definido. Esto no verificará si el valor es nulo o no está definido.

Operador de aserción no nulo de TypeScript

// Compiled with --strictNullChecks
function validateEntity(e?: Entity) {
    // Throw exception if e is null or invalid entity
}

function processEntity(e?: Entity) {
    validateEntity(e);
    let s = e!.name;  // Assert that e is non-null and access name
}

44
No es lo mismo que ?porque afirma que el valor está definido. ?se espera que falle silenciosamente / evalúe a falso. De todos modos, es bueno saberlo.
fracz el

1
Ahora que lo pienso ... Esta respuesta no tiene sentido, porque no hace la "navegación segura" que hace el operador de C #.
Jose A

55
Sin embargo, esto respondió mi pregunta. ¿Sabía sobre? de c # y lo probé en mecanografiado. ¡No funcionó, pero lo vi! existió pero no sabía lo que hacía. Me preguntaba si era lo mismo, hice una búsqueda en Google y encontré mi camino a esta pregunta que me informó que no, que son diferentes.
Llewey

11

El operador de encadenamiento opcional Elvis (?.) Es compatible con TypeScript 3.7.

Puede usarlo para verificar valores nulos: cats?.miowsdevuelve nulo si cats es nulo o no está definido.

También puede usarlo para llamar a métodos opcionales: cats.doMiow?.(5)llamará a doMiow si existe.

El acceso a la propiedad también es posible: cats?.['miows'] .

Referencia: https://devblogs.microsoft.com/typescript/announcing-typescript-3-7-beta/


Corríjame, pero el operador de Elvis está al menos en Kotlin ?:. Tiene una referencia?
rekire


1
Pronto será compatible con JS simple - developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Mottie

1
El anuncio de la versión TypeScript 3.7 lo menciona: devblogs.microsoft.com/typescript/announcing-typescript-3-7
György Balássy

10

El operador ?.no es compatible con TypeScript versión 2.0 .

Entonces uso la siguiente función:

export function o<T>(someObject: T, defaultValue: T = {} as T) : T {
    if (typeof someObject === 'undefined' || someObject === null)
        return defaultValue;
    else
        return someObject;
}

el uso se ve así:

o(o(o(test).prop1).prop2

Además, puede establecer un valor predeterminado:

o(o(o(o(test).prop1).prop2, "none")

Funciona muy bien con IntelliSense en Visual Studio.


1
¡Esto es exactamente lo que estaba buscando! Funciona en mecanografiado 2.1.6.
Rajab Shakirov

55
o podrías llamarlo elvis<T>;-)
Simon_Weaver

3
Simon_Weaver, lo llamo "payaso triste": o (
VeganHunter

5

¡Finalmente está aquí!

Aquí están algunos ejemplos:

// properties
foo?.bar
foo?.bar()
foo?.bar.baz()
foo?.bar?.baz()

// indexing
foo?.[0]
foo?.['bar']

// check if a function is defined before invoking
foo?.()
foo.bar?.()
foo?.bar?.()

Pero no funciona exactamente igual que su suposición.

En lugar de evaluar

foo?.bar

a este pequeño fragmento de código todos estamos acostumbrados a escribir

foo ? foo.bar : null

en realidad se evalúa a

(foo === null || foo === undefined) ?
    undefined :
    foo.bar

que funciona para todos los valores de falsey como una cadena vacía, 0 o falso.

Simplemente no tengo una explicación de por qué no lo compilan para foo == null


3

Creamos este método de utilidad mientras trabajamos en Phonetradr que puede brindarle acceso seguro a las propiedades profundas con Typecript:

/**
 * Type-safe access of deep property of an object
 *
 * @param obj                   Object to get deep property
 * @param unsafeDataOperation   Function that returns the deep property
 * @param valueIfFail           Value to return in case if there is no such property
 */
export function getInSafe<O,T>(obj: O, unsafeDataOperation: (x: O) => T, valueIfFail?: any) : T {
    try {
        return unsafeDataOperation(obj)
    } catch (error) {
        return valueIfFail;
    }
}

//Example usage:
getInSafe(sellTicket, x => x.phoneDetails.imeiNumber, '');

//Example from above
getInSafe(foo, x => x.bar.check, null);


¡¡Frio!! ¿Hay alguna advertencia? Tengo una clase de envoltura con aproximadamente 20 captadores para escribir, cada uno de ellos tiene el siguiente tipo de retorno, y todos los campos deben verificarse de forma nulareturn this.entry.fields.featuredImage.fields.file.url;
Drenai el

La única advertencia podría ser un impacto en el rendimiento, pero no estoy calificado para hablar sobre cómo los diferentes JITers manejan esto.
Ray Suelzer

2

En general, no recomiendo este enfoque (tenga cuidado con las preocupaciones de rendimiento), pero puede usar el operador de propagación para clonar superficialmente un objeto, al que luego puede acceder a la propiedad.

 const person = { personId: 123, firstName: 'Simon' };
 const firstName = { ...person }.firstName;

Esto funciona porque el tipo de 'nombre' se 'propaga' a través.

Usaré esto con más frecuencia cuando tenga una find(...)expresión que pueda devolver nulo y necesite una sola propiedad:

 // this would cause an error (this ID doesn't exist)
 const people = [person];
 const firstName2 = people.find(p => p.personId == 999).firstName;

 // this works - but copies every property over so raises performance concerns
 const firstName3 = { ...people.find(p => p.personId == 999) }.firstName;

Puede haber algunos casos extremos con la forma en que el mecanografiado infiere tipos y esto no se compilará, pero esto generalmente debería funcionar.


2

Se llama encadenamiento opcional y está en Typecript 3.7

El encadenamiento opcional nos permite escribir código donde podemos detener inmediatamente la ejecución de algunas expresiones si nos encontramos con un valor nulo o indefinido


0

Como se respondió anteriormente, actualmente todavía se está considerando, pero ya lleva varios años muerto en el agua .

Sobre la base de las respuestas existentes, aquí está la versión manual más concisa que se me ocurre:

jsfiddle

function val<T>(valueSupplier: () => T): T {
  try { return valueSupplier(); } catch (err) { return undefined; }
}

let obj1: { a?: { b?: string }} = { a: { b: 'c' } };
console.log(val(() => obj1.a.b)); // 'c'

obj1 = { a: {} };
console.log(val(() => obj1.a.b)); // undefined
console.log(val(() => obj1.a.b) || 'Nothing'); // 'Nothing'

obj1 = {};
console.log(val(() => obj1.a.b) || 'Nothing'); // 'Nothing'

obj1 = null;
console.log(val(() => obj1.a.b) || 'Nothing'); // 'Nothing'

Simplemente falla silenciosamente cuando faltan errores de propiedad. Recurre a la sintaxis estándar para determinar el valor predeterminado, que también se puede omitir por completo.


Aunque esto funciona para casos simples, si necesita cosas más complejas, como llamar a una función y luego acceder a una propiedad en el resultado, también se tragan otros errores. Mal diseño

En el caso anterior, una versión optimizada de la otra respuesta publicada aquí es la mejor opción:

jsfiddle

function o<T>(obj?: T, def: T = {} as T): T {
    return obj || def;
}

let obj1: { a?: { b?: string }} = { a: { b: 'c' } };
console.log(o(o(o(obj1).a)).b); // 'c'

obj1 = { a: {} };
console.log(o(o(o(obj1).a)).b); // undefined
console.log(o(o(o(obj1).a)).b || 'Nothing'); // 'Nothing'

obj1 = {};
console.log(o(o(o(obj1).a)).b || 'Nothing'); // 'Nothing'

obj1 = null;
console.log(o(o(o(obj1).a)).b || 'Nothing'); // 'Nothing'

Un ejemplo más complejo:

o(foo(), []).map((n) => n.id)

También puede ir hacia otro lado y usar algo como Lodash ' _.get(). Es conciso, pero el compilador no podrá juzgar la validez de las propiedades utilizadas:

console.log(_.get(obj1, 'a.b.c'));

0

Todavía no (a partir de septiembre de 2019), pero dado que el "operador de navegación segura" se encuentra ahora en la Etapa 3 , se está implementando en TypeScript.

Mire este número para actualizaciones:

https://github.com/microsoft/TypeScript/issues/16

Varios motores tienen implementaciones tempranas:

JSC: https://bugs.webkit.org/show_bug.cgi?id=200199

V8: https://bugs.chromium.org/p/v8/issues/detail?id=9553

SM: https://bugzilla.mozilla.org/show_bug.cgi?id=1566143

(a través de https://github.com/tc39/proposal-optional-chaining/issues/115#issue-475422578 )

Puede instalar un complemento para admitirlo ahora:

npm install --save-dev ts-optchain

En tu tsconfig.json:

// tsconfig.json
{
    "compilerOptions": {
        "plugins": [
            { "transform": "ts-optchain/transform" },
        ]
    },
}

Espero que esta respuesta esté desactualizada en los próximos 6 meses más o menos, pero espero que mientras tanto ayude a alguien.


-1

_.get(obj, 'address.street.name')funciona muy bien para JavaScript donde no tienes tipos. ¡Pero para TypeScript necesitamos el verdadero operador de Elvis!

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.