Mecanografíe el operador de navegación segura (?.) O (!.) Y rutas de propiedad nulas


109

En las plantillas de Angular2, el operador seguro ?.funciona, pero no en el component.tsuso de TypeScript 2.0. Además, el operador de navegación segura (!.) No funciona.

Por ejemplo...

Este TypeScript

if (a!.b!.c) { }

compila a este JavaScript

if (a.b.c) { }

Pero cuando lo ejecuto, aparece el siguiente error:

No se puede leer la propiedad 'b' de indefinido

¿Existe alguna alternativa a lo siguiente?

if (a && a.b && a.b.c) { }

7
Los operadores mecanografiados solo existen para la compilación, no están presentes en el javascript compilado. El error que publicaste es un error de tiempo de ejecución.
— Nitzan Tomer

o (o (o (o (prueba) .level1) .level2) .level3 o o (o (o (o (a) .b) .c) .d stackoverflow.com/a/35169378/3914072 esta solución funciona muy bien para nosotros en tiempo de compilación y es con tipos seguros
— Rajab Shakirov

Respuestas:


68

Desde que se lanzó TypeScript 3.7, ahora puede usar el encadenamiento opcional.

Ejemplo de propiedad:

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

Esto es equivalente a:

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

Además puedes llamar a:

Llamada opcional

function(otherFn: (par: string) => void) {
   otherFn?.("some value");
}

otherFn se llamará solo si otherFn no será igual a nulo o indefinido

Uso de encadenamiento opcional en declaración IF

Esta:

if (someObj && someObj.someProperty) {
    // ...
}

se puede reemplazar ahora con esto

if (someObj?.someProperty) {
    // ...
}

Árbitro. https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html


esto sigue arrojando el error esperado de Expresión al compilar, funciona si uso en !lugar de ?"mecanografiado": "~ 3.5.3"
— Scaramouche

3
@Scaramouche esto se debe a que el operador de encadenamiento opcional ( ?.) se introdujo en 3.7 y está ejecutando 3.5.3. En versiones anteriores a la 3.7, TypeScript asume que ?se usa en la forma abreviada if ( foo ? bar : baz) y, por lo tanto, espera el resto de la expresión
— George

134

!es un operador de aserción no nulo (expresión posterior a la corrección): solo le dice al comprobador de tipo que está seguro de que ano es nullo undefined.

la operación a!produce un valor del tipo de acon nully undefinedexcluido


Encadenamiento opcional finalmente hizo que mecanografiado ( 3.7 ) 🎉

El operador de encadenamiento opcional ?.permite leer el valor de una propiedad ubicada en lo profundo de una cadena de objetos conectados sin tener que validar expresamente que cada referencia en la cadena es válida. El ?.operador funciona de manera similar al .operador de encadenamiento, excepto que en lugar de causar un error si una referencia es nula ( nullo undefined), la expresión cortocircuita con un valor de retorno de undefined. Cuando se usa con llamadas a funciones, regresa undefinedsi la función dada no existe.

Sintaxis :

obj?.prop // Accessing object's property
obj?.[expr] // Optional chaining with expressions
arr?.[index] // Array item access with optional chaining
func?.(args) // Optional chaining with function calls

Presta atencion :

El encadenamiento opcional no es válido en el lado izquierdo de una asignación

const object = {};
object?.property = 1; // Uncaught SyntaxError: Invalid left-hand side in assignment

12
Para aquellos interesados ​​en verificar el estado de esto. TypeScript planea agregarlo una vez que ECMAScript agregue soporte. ECMAScript está rastreando este problema aquí .
— Ritmo

o (o (o (o (prueba) .level1) .level2) .level3 o o (o (o (o (a) .b) .c) .d stackoverflow.com/a/35169378/3914072 esta solución funciona muy bien para nosotros en tiempo de compilación y es con tipos seguros
— Rajab Shakirov

zona de juegos de la nueva función (se lanzará el 5 de noviembre): typescriptlang.org/play/?ts=3.7.0-pr-33294-11#code/…
— Nisim Joseph

1
@ aleksey-l: Puede que no sea una mala idea tachar las cosas antes de la "última actualización". ¡Ayudará a las personas que no leen de arriba a abajo (es decir, demasiado rápido)! :)
— Aidin

1
Esto es genial, no tenía idea de que lo pudieras usar para llamadas a funciones. Estaba buscando una solución y encontré tu respuesta, ¡así que gracias!
— Geoff Davids

22

Actualizar:

Planeado en el alcance de la versión 3.7
https://github.com/microsoft/TypeScript/issues/33352


Puedes intentar escribir una función personalizada como esa.

La principal ventaja del enfoque es una verificación de tipo e intellisense parcial.

export function nullSafe<T, 
    K0 extends keyof T, 
    K1 extends keyof T[K0],
    K2 extends keyof T[K0][K1],
    K3 extends keyof T[K0][K1][K2],
    K4 extends keyof T[K0][K1][K2][K3],
    K5 extends keyof T[K0][K1][K2][K3][K4]>
    (obj: T, k0: K0, k1?: K1, k2?: K2, k3?: K3, k4?: K4, k5?: K5) {
    let result: any = obj;

    const keysCount = arguments.length - 1;
    for (var i = 1; i <= keysCount; i++) {
        if (result === null || result === undefined) return result;
        result = result[arguments[i]];
    }

    return result;
}

Y uso (admite hasta 5 parámetros y se puede ampliar):

nullSafe(a, 'b', 'c');

Ejemplo en patio de recreo .


1
Esto funciona bien, pero tiene un problema: VSCode no mostrará esto cuando use "Buscar todas las referencias" en una de las claves.
— Ivan Koshelev

¿Qué tipo de verificación de tipos? En el momento en que pasa cadenas como parámetros, ha perdido el tipo. Si es así, es mejor que use lodash.get
— un33k

16

Otra alternativa que usa una biblioteca externa es _.has () de Lodash .

P.ej

_.has(a, 'b.c')

es igual a

(a && a.b && a.b.c)

EDITAR: Como se señaló en los comentarios, pierde la inferencia de tipo de TypeScript cuando usa este método. Por ejemplo, suponiendo que los objetos de uno estén escritos correctamente, se obtendría un error de compilación con (a && ab && abz) si z no está definido como un campo del objeto b. Pero usando _.has (a, 'b.z'), no se obtendría ese error.


Buen consejo, aunque no hay otra solución disponible, esta parece ser una buena alternativa.
— Dimas Crocco

3
Pero, ¿cómo funciona con la inferencia de tipos de TypeScript?
— VitalyB

@VitalyB probablemente te refieres a la importación de la interfaz correspondiente de lodash. (importar * como _ de 'lodash';)
— Ankur Arora

1
@AnkurArora, creo que lo que VitalyB significa es que no se realiza ninguna verificación de tipos con el método Lodash. Por ejemplo, suponiendo que los objetos de uno estén escritos correctamente, se obtendría un error de compilación con (a && ab && abz) si z no está definido como un campo del objeto b. Pero usando _.has (a, 'b.z'), no se obtendría ese error. En cuanto a la declaración de importación, sí, definitivamente es necesaria.
— TiaanM

1
@TiaanM Eso es exactamente lo que quise decir. Sin embargo, acabo de encontrar una nueva biblioteca que cubre el problema de los tipos: stackoverflow.com/a/55462483/126574
— VitalyB

3

Una nueva biblioteca llamada ts-optchain proporciona esta funcionalidad y, a diferencia de la solución de lodash, también mantiene sus tipos seguros, aquí hay una muestra de cómo se usa (tomada del archivo Léame):

import { oc } from 'ts-optchain';

interface I {
  a?: string;
  b?: {
    d?: string;
  };
  c?: Array<{
    u?: {
      v?: number;
    };
  }>;
  e?: {
    f?: string;
    g?: () => string;
  };
}

const x: I = {
  a: 'hello',
  b: {
    d: 'world',
  },
  c: [{ u: { v: -100 } }, { u: { v: 200 } }, {}, { u: { v: -300 } }],
};

// Here are a few examples of deep object traversal using (a) optional chaining vs
// (b) logic expressions. Each of the following pairs are equivalent in
// result. Note how the benefits of optional chaining accrue with
// the depth and complexity of the traversal.

oc(x).a(); // 'hello'
x.a;

oc(x).b.d(); // 'world'
x.b && x.b.d;

oc(x).c[0].u.v(); // -100
x.c && x.c[0] && x.c[0].u && x.c[0].u.v;

oc(x).c[100].u.v(); // undefined
x.c && x.c[100] && x.c[100].u && x.c[100].u.v;

oc(x).c[100].u.v(1234); // 1234
(x.c && x.c[100] && x.c[100].u && x.c[100].u.v) || 1234;

oc(x).e.f(); // undefined
x.e && x.e.f;

oc(x).e.f('optional default value'); // 'optional default value'
(x.e && x.e.f) || 'optional default value';

// NOTE: working with function value types can be risky. Additional run-time
// checks to verify that object types are functions before invocation are advised!
oc(x).e.g(() => 'Yo Yo')(); // 'Yo Yo'
((x.e && x.e.g) || (() => 'Yo Yo'))();

2

Sobre la base de la respuesta de @ Pvl, también puede incluir seguridad de tipos en su valor devuelto si usa anulaciones:

function dig<
  T,
  K1 extends keyof T
  >(obj: T, key1: K1): T[K1];

function dig<
  T,
  K1 extends keyof T,
  K2 extends keyof T[K1]
  >(obj: T, key1: K1, key2: K2): T[K1][K2];

function dig<
  T,
  K1 extends keyof T,
  K2 extends keyof T[K1],
  K3 extends keyof T[K1][K2]
  >(obj: T, key1: K1, key2: K2, key3: K3): T[K1][K2][K3];

function dig<
  T,
  K1 extends keyof T,
  K2 extends keyof T[K1],
  K3 extends keyof T[K1][K2],
  K4 extends keyof T[K1][K2][K3]
  >(obj: T, key1: K1, key2: K2, key3: K3, key4: K4): T[K1][K2][K3][K4];

function dig<
  T,
  K1 extends keyof T,
  K2 extends keyof T[K1],
  K3 extends keyof T[K1][K2],
  K4 extends keyof T[K1][K2][K3],
  K5 extends keyof T[K1][K2][K3][K4]
  >(obj: T, key1: K1, key2: K2, key3: K3, key4: K4, key5: K5): T[K1][K2][K3][K4][K5];

function dig<
  T,
  K1 extends keyof T,
  K2 extends keyof T[K1],
  K3 extends keyof T[K1][K2],
  K4 extends keyof T[K1][K2][K3],
  K5 extends keyof T[K1][K2][K3][K4]
  >(obj: T, key1: K1, key2?: K2, key3?: K3, key4?: K4, key5?: K5):
  T[K1] |
  T[K1][K2] |
  T[K1][K2][K3] |
  T[K1][K2][K3][K4] |
  T[K1][K2][K3][K4][K5] {
    let value: any = obj && obj[key1];

    if (key2) {
      value = value && value[key2];
    }

    if (key3) {
      value = value && value[key3];
    }

    if (key4) {
      value = value && value[key4];
    }

    if (key5) {
      value = value && value[key5];
    }

    return value;
}

Ejemplo en patio de recreo .

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.