Escreva o operador de navegação segura (?.) Ou (!.) E os caminhos de propriedade nulos


109

Nos modelos Angular2, o operador seguro ?.funciona, mas não no component.tsuso do TypeScript 2.0. Além disso, o operador de navegação segura (!.) Não funciona.

Por exemplo...

Este TypeScript

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

compila para este JavaScript

if (a.b.c) { }

Mas quando eu o executo, obtenho o seguinte erro:

Não é possível ler a propriedade 'b' de indefinido

Existe alguma alternativa para o seguinte?

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

7
Os operadores de digitação existem apenas para compilação, eles não estão presentes no javascript compilado. O erro que você postou é um erro de tempo de execução.
Nitzan Tomer

o (o (o (o (teste) .level1) .level2) .level3 ou o (o (o (o (a) .b) .c) .d stackoverflow.com/a/35169378/3914072 esta solução funciona muito bem para nós em tempo de compilação e com tipos seguros
Rajab Shakirov

Respostas:


68

Como o TypeScript 3.7 foi lançado, você pode usar o encadeamento opcional agora.

Exemplo de propriedade:

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

Isso é equivalente a:

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

Além disso, você pode ligar para:

Chamada Opcional

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

otherFn será chamado apenas se otherFn não for igual a nulo ou indefinido

Uso de encadeamento opcional na instrução IF

Este:

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

pode ser substituído agora por este

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

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


isso continua gerando um erro esperado de Expressão ao compilar, ele funciona se eu usar em !vez de ?em "texto digitado": "~ 3.5.3"
Scaramouche,

3
@Scaramouche isso ocorre porque o operador de encadeamento opcional ( ?.) foi introduzido no 3.7 e você está executando o 3.5.3. Nas versões anteriores à 3.7, o TypeScript assume que ?está sendo usado na forma abreviada if form ( foo ? bar : baz) e, portanto, espera o resto da expressão
George

134

!é um operador de asserção não nulo (expressão pós-correção) - está apenas dizendo para digitar verificador que você tem certeza de que anão é nullou undefined.

a operação a!produz um valor do tipo acom nulle undefinedexcluído


O encadeamento opcional finalmente chegou ao texto datilografado ( 3.7 ) 🎉

O operador de encadeamento opcional ?.permite a leitura do valor de uma propriedade localizada no fundo de uma cadeia de objetos conectados sem ter que validar expressamente que cada referência na cadeia é válida. O ?.operador funciona de forma semelhante ao .operador de encadeamento, exceto que, em vez de causar um erro se uma referência for nula ( nullou undefined), a expressão entra em curto-circuito com um valor de retorno de undefined. Quando usado com chamadas de função, ele retorna undefinedse a função fornecida não existir.

Sintaxe :

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

Preste atenção :

O encadeamento opcional não é válido no lado esquerdo de uma tarefa

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

12
Para os interessados ​​em verificar o status disso. O TypeScript está planejando adicioná-lo assim que o ECMAScript adicionar suporte. O ECMAScript está rastreando esse problema aqui .
Pace

o (o (o (o (teste) .level1) .level2) .level3 ou o (o (o (o (a) .b) .c) .d stackoverflow.com/a/35169378/3914072 esta solução funciona muito bem para nós em tempo de compilação e com tipos seguros
Rajab Shakirov

playground do novo recurso (será lançado em 5 de novembro): typescriptlang.org/play/?ts=3.7.0-pr-33294-11#code/…
Nisim Joseph

1
@aleksey-l: Pode não ser uma má ideia rasurar os itens antes da "última atualização". Isso ajudará as pessoas que não lêem de cima para baixo (ou seja, rápido demais)! :)
Aidin

1
Isso é tão legal - eu não tinha ideia de que você poderia usá-lo para chamadas de função. Estava procurando uma solução e me deparei com sua resposta, então obrigado!
Geoff Davids

22

Atualizar:

Planejado no escopo da versão 3.7
https://github.com/microsoft/TypeScript/issues/33352


Você pode tentar escrever uma função personalizada como essa.

A principal vantagem da abordagem é uma verificação 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;
}

E uso (suporta até 5 parâmetros e pode ser estendido):

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

Exemplo no playground .


1
Isso funciona bem, mas tem um problema - o VSCode não mostrará isso ao usar "Find All References" em uma das teclas.
Ivan Koshelev

Que tipo de verificação de tipo? No momento em que você passa as strings como parâmetros, você perde o tipo. Se sim, é melhor usar apenas lodash.get
un33k

16

Outra alternativa que usa uma biblioteca externa é _.has () do Lodash .

Por exemplo

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

é igual a

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

EDIT: Conforme observado nos comentários, você perde a inferência de tipo do Typescript ao usar este método. Por exemplo, supondo que os objetos de alguém sejam digitados corretamente, obter-se-ia um erro de compilação com (a && ab && abz) se z não for definido como um campo do objeto b. Mas usando _.has (a, 'b.z'), não se obteria esse erro.


Boa dica, embora nenhuma outra solução esteja disponível, esta parece ser uma boa alternativa.
Dimas Crocco

3
Mas como funciona com a inferência de tipo do TypeScript?
VitalyB

@VitalyB provavelmente significa a importação da interface correspondente do Lodash. (importação * como _ de 'lodash';)
Ankur Arora

1
@AnkurArora, acho que o que VitalyB quer dizer é que nenhuma verificação de tipo é feita usando o método Lodash. Por exemplo, supondo que os objetos de alguém sejam digitados corretamente, obter-se-ia um erro de compilação com (a && ab && abz) se z não for definido como um campo do objeto b. Mas usando _.has (a, 'b.z'), não se obteria esse erro. Quanto à declaração de importação, sim, é definitivamente necessária.
TiaanM

1
@TiaanM Isso é exatamente o que eu quis dizer. Acabei de encontrar uma nova biblioteca, no entanto, que cobre o problema de tipos: stackoverflow.com/a/55462483/126574
VitalyB de

3

Uma nova biblioteca chamada ts-optchain fornece essa funcionalidade e, ao contrário da solução lodash, ela também mantém seus tipos seguros. Aqui está um exemplo de como ela é usada (retirado do leia-me):

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

Com base na resposta de @Pvl, você pode incluir segurança de tipo em seu valor retornado também se usar substituições:

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;
}

Exemplo no playground .

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.