Typescript prend-il en charge le?. opérateur? (Et comment ça s'appelle?)

337

Typescript prend-il actuellement (ou prévoit-il) de prendre en charge l' opérateur de navigation?.

c'est à dire:

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

En outre, existe-t-il un nom plus commun pour cet opérateur (il est incroyablement difficile de chercher sur Google).

Marty Pitt
la source
3
@mattytommo vous avez cela en c #, son appelé l'opérateur de coalescence nul et utilise le ?? syntaxe weblogs.asp.net/scottgu/archive/2007/09/20/…
basarat
2
@BasaratAli Malheureusement non, la fusion n'est pas un problème property ?? property2, mais si vous essayez property.company ?? property1.companyet que vous êtes propertynul, vous obtenez unNullReferenceException
mattytommo
1
@mattytommo Merci de l'avoir maintenant '?.' absorbe en fait toutes les références nulles dans la chaîne. Doux.
basarat
9
@mattytommo cela existe maintenant pour C #: msdn.microsoft.com/en-us/library/dn986595.aspx
Highmastdon
9
Le représentant de Microsoft qui nous a visités l'a appelé l'opérateur Elvis car le point d'interrogation ressemble aux cheveux d'Elvis et au microphone dans
lequel

Réponses:

168

Mise à jour : elle est prise en charge à partir de TypeScript 3.7 et appelée chaînage facultatif : https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#optional-chaining

Je ne trouve aucune référence à cela dans la spécification du langage TypeScript .

Quant à savoir comment appeler cet opérateur dans CoffeeScript, il est appelé l' opérateur existentiel (en particulier, la "variante d'accesseur" de l'opérateur existentiel).

De la documentation de CoffeeScript sur les opérateurs :

La variante d'accesseur de l'opérateur existentiel ?.peut être utilisée pour absorber des références nulles dans une chaîne de propriétés. Utilisez-le à la place de l'accesseur point .dans les cas où la valeur de base peut être nulle ou non définie .

Ainsi, la variante d'accesseur de l'opérateur existentiel semble être la bonne façon de se référer à cet opérateur; et TypeScript ne semble pas actuellement le prendre en charge (bien que d' autres aient exprimé le désir de cette fonctionnalité ).

Donut
la source
28
msgstr "variante accesseur de l 'opérateur existentiel". Naturellement. Tellement accrocheur, il est presque impossible d'oublier. :). Merci pour la réponse extrêmement approfondie.
Marty Pitt
1
@MartyPitt Bien sûr! Je suis d'accord, j'aimerais voir a) une adoption plus large d'un opérateur comme celui-ci (C # s'il vous plaît!) Et b) un meilleur nom (l'opérateur de "navigation sécurisée" de votre blog lié a une belle sonnerie).
Donut
2
Angular implémente ceci dans ses modèles: angular.io/guide/…
Enzoaeneas
5
Dans certaines autres langues, il est appelé l'opérateur "Elvis"
k0enf0rNL
4
Il est annoncé pour TypeScript 3.7.0 ( github.com/microsoft/TypeScript/issues/… )
c_froehlich
146

Pas aussi sympa qu'un single?, Mais ça marche:

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

Vous pouvez utiliser autant de && que vous le souhaitez:

var thing = foo && foo.bar && foo.bar.check && foo.bar.check.x || null;
A. KR
la source
33
&& évalue tant que la déclaration est vraie. S'il est vrai, il renvoie la dernière valeur. S'il est faux, il renvoie la première valeur évaluée à faux. Cela peut être 0, nul, faux, etc. || renvoie la première valeur évaluée à true.
A. KR
34
Ne fonctionne pas bien si la barre est définie mais prend la valeur false (comme booléen false ou zéro).
mt_serg
96

Ceci est défini dans la spécification de chaînage facultatif ECMAScript, nous devrions donc probablement faire référence au chaînage facultatif lorsque nous en discuterons. Mise en œuvre probable:

const result = a?.b?.c;

Le long et le court de celui-ci est que l'équipe TypeScript attend que la spécification ECMAScript soit resserrée, de sorte que leur implémentation puisse être ininterrompue à l'avenir. S'ils implémentaient quelque chose maintenant, cela finirait par nécessiter des changements majeurs si ECMAScript redéfinissait leurs spécifications.

Voir spécification de chaînage facultative

Là où quelque chose ne sera jamais du JavaScript standard, l'équipe TypeScript peut implémenter comme bon lui semble, mais pour les futurs ajouts ECMAScript, ils veulent préserver la sémantique même s'ils donnent un accès anticipé, comme ils l'ont fait pour tant d'autres fonctionnalités.

Raccourcis

Ainsi, tous les opérateurs funky JavaScripts sont disponibles, y compris les conversions de type telles que ...

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

Solution manuelle

Mais revenons à la question. J'ai un exemple obtus de la façon dont vous pouvez faire une chose similaire en JavaScript (et donc TypeScript), bien que je ne suggère certainement pas que c'est une grâce car la fonctionnalité que vous recherchez vraiment.

(foo||{}).bar;

Donc, si fooest undefinedle résultat est undefinedet si fooest défini et possède une propriété nommée barqui a une valeur, le résultat est cette valeur.

Je mets un exemple sur JSFiddle .

Cela semble assez sommaire pour des exemples plus longs.

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

Fonction chaîne

Si vous cherchez désespérément une version plus courte alors que les spécifications sont toujours en suspens, j'utilise cette méthode dans certains cas. Il évalue l'expression et renvoie une valeur par défaut si la chaîne ne peut pas être satisfaite ou se retrouve nulle / non définie (notez que !=c'est important ici, nous ne voulons pas l'utiliser !==car nous voulons un peu de jonglage positif ici).

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'));
Fenton
la source
1
intéressant mais dans mon cas en (this.loop || {}).nativeElementdisant Property 'nativeElement' does not exist on type '{}'. any this.looptypeof angular.io/api/core/ElementRef
kuncevic.dev
@Kuncevic - vous devez soit ... 1) fournir une valeur par défaut compatible à la place de {}, ou 2) utiliser une assertion de type pour désactiver le compilateur.
Fenton
Supposer fooest un objet réellement utile: (foo || {}).barne va généralement pas être compilé en tapuscrit car {}il ne sera pas du même type que foo. C'est le problème que la solution de @ VeganHunter vise à éviter.
Simon_Weaver
1
@Simon_Weaver then (foo || {bar}). Bar permettra au compilateur de fonctionner sans problème et je pense que la verbosité est acceptable.
Harps
@harps en fait, cela ne se compile que si la barre est définie comme une variable, ce qui ne serait probablement pas
Simon_Weaver
83

Mise à jour: Oui, c'est supporté maintenant!

Il vient de sortir avec TypeScript 3.7: https://devblogs.microsoft.com/typescript/announcing-typescript-3-7/

Il s'agit du chaînage facultatif : https://devblogs.microsoft.com/typescript/announcing-typescript-3-7/#optional-chaining

Avec elle, les éléments suivants:

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

est équivalent à:

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

Ancienne réponse

Il y a une demande de fonctionnalité ouverte pour cela sur github où vous pouvez exprimer votre opinion / désir: https://github.com/Microsoft/TypeScript/issues/16

basarat
la source
36

Modifier le 13 novembre 2019!

Depuis le 5 novembre 2019, TypeScript 3.7 est livré et prend désormais en charge ?. l'opérateur de chaînage facultatif 🎉🎉🍾🍾🎉 !!!

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


À des fins historiques uniquement:

Éditer: j'ai mis à jour la réponse grâce au commentaire de fracz.

TypeScript 2.0 publié !.Ce n'est pas la même chose que?. (Safe Navigator en C #)

Voir cette réponse pour plus de détails:

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

Cela indiquera uniquement au compilateur que la valeur n'est pas nulle ou non définie. Cela ne vérifiera pas si la valeur est nulle ou non définie.

Opérateur d'assertion non nul 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
}
Jose A
la source
4
Pas le même que ?parce qu'il affirme que la valeur est définie. ?devrait échouer silencieusement / évaluer à faux. Quoi qu'il en soit, bon à savoir.
fracz
1
Maintenant que j'y pense ... Cette réponse est assez inutile, car elle ne fait pas la "navigation sécurisée" que fait l'opérateur C #.
Jose A
5
Cela a répondu à ma question, cependant. Je savais? de c # et l'ai essayé en tapuscrit. Ça n'a pas marché, mais j'ai vu ça! existait mais ne savait pas ce qu'il faisait. Je me suis demandé si c'était la même chose, j'ai fait une recherche Google et j'ai trouvé mon chemin vers cette question qui m'a informé que non, ils sont différents.
Llewey
11

L'opérateur de chaînage facultatif Elvis (?.) Est pris en charge dans TypeScript 3.7.

Vous pouvez l'utiliser pour vérifier les valeurs nulles: cats?.miowsretourne null si cats est nul ou non défini.

Vous pouvez également l'utiliser pour appeler une méthode facultative: cats.doMiow?.(5) appellera doMiow s'il existe.

Accès La propriété est également possible: cats?.['miows'].

Référence: https://devblogs.microsoft.com/typescript/announcing-typescript-3-7-beta/

superluminaire
la source
Veuillez me corriger, mais l'opérateur d'Elvis est au moins à Kotlin ?:. Avez-vous une référence?
rekire
@rekire: devblogs.microsoft.com/typescript/…
superluminaire
1
Il sera bientôt pris en charge dans JS ordinaire - developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Mottie
1
L'annonce de la version TypeScript 3.7 le mentionne: devblogs.microsoft.com/typescript/announcing-typescript-3-7
György Balássy
10

L'opérateur ?.n'est pas pris en charge dans TypeScript version 2.0 .

J'utilise donc la fonction suivante:

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

l'utilisation ressemble à ceci:

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

de plus, vous pouvez définir une valeur par défaut:

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

Cela fonctionne très bien avec IntelliSense dans Visual Studio.

VeganHunter
la source
1
Ceci est exactement ce que je cherchais! Il fonctionne en tapuscrit 2.1.6.
Rajab Shakirov
5
ou vous pourriez l'appeler elvis<T>;-)
Simon_Weaver
3
Simon_Weaver, je l'appelle "triste clown": o (
VeganHunter
5

C'est enfin là!

Voici quelques exemples:

// 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?.()

Mais cela ne fonctionne pas exactement de la même manière que votre hypothèse.

Au lieu d'évaluer

foo?.bar

à ce petit extrait de code, nous sommes tous habitués à écrire

foo ? foo.bar : null

il évalue en fait

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

qui fonctionne pour toutes les valeurs de falsey comme une chaîne vide, 0 ou false.

Je n'ai tout simplement pas d'explication pourquoi ils ne le compilent pas foo == null

zoran404
la source
3

Nous avons créé cette méthode util tout en travaillant sur Phonetradr qui peut vous donner un accès sécurisé aux propriétés profondes avec Typescript:

/**
 * 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);

phidias
la source
Cool!! Y a-t-il des mises en garde? J'ai une classe wrapper avec environ 20 getters à écrire, chacun d'eux a le type de retour suivant - et tous les champs doivent être vérifiés nullreturn this.entry.fields.featuredImage.fields.file.url;
Drenai
La seule mise en garde pourrait être un impact sur les performances, mais je ne suis pas qualifié pour parler de la façon dont les différents JITers gèrent cela.
Ray Suelzer
2

Je ne recommande généralement pas cette approche (faites attention aux problèmes de performances), mais vous pouvez utiliser l'opérateur de propagation pour cloner peu profondément un objet, sur lequel vous pouvez ensuite accéder à la propriété.

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

Cela fonctionne car le type de 'firstName' est 'propagé' à travers.

Je vais l'utiliser le plus souvent lorsque j'ai une find(...)expression qui peut retourner null et que j'en ai besoin d'une seule propriété:

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

Il peut y avoir des cas marginaux avec la manière dont le tapuscrit déduit les types et cela ne se compile pas, mais cela devrait généralement fonctionner.

Simon_Weaver
la source
2

C'est ce qu'on appelle le chaînage facultatif et c'est dans le script de type 3.7

Le chaînage facultatif nous permet d'écrire du code où nous pouvons immédiatement arrêter d'exécuter certaines expressions si nous rencontrons un null ou undefined

Damian Green
la source
0

Comme indiqué précédemment, il est actuellement encore à l'étude, mais il est mort dans l'eau depuis quelques années.

S'appuyant sur les réponses existantes, voici la version manuelle la plus concise à laquelle je puisse penser:

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'

Il échoue simplement en silence sur les erreurs de propriété manquantes. Il revient à la syntaxe standard pour déterminer la valeur par défaut, qui peut également être complètement omise.


Bien que cela fonctionne pour les cas simples, si vous avez besoin de choses plus complexes telles que l'appel d'une fonction, puis l'accès à une propriété sur le résultat, toutes les autres erreurs sont également avalées. Mauvaise conception.

Dans le cas ci-dessus, une meilleure version de l'autre réponse publiée ici est la meilleure option:

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 exemple plus complexe:

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

Vous pouvez également aller dans l'autre sens et utiliser quelque chose comme Lodash ' _.get(). C'est concis, mais le compilateur ne pourra pas juger de la validité des propriétés utilisées:

console.log(_.get(obj1, 'a.b.c'));
Benny Bottema
la source
0

Pas encore (en septembre 2019), mais comme "l'opérateur de navigation en toute sécurité" est désormais à l'étape 3 , il est implémenté en TypeScript.

Surveillez ce numéro pour les mises à jour:

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

Plusieurs moteurs ont des implémentations précoces:

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

(via https://github.com/tc39/proposal-optional-chaining/issues/115#issue-475422578 )

Vous pouvez installer un plugin pour le prendre en charge maintenant:

npm install --save-dev ts-optchain

Dans votre tsconfig.json:

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

Je m'attends à ce que cette réponse soit obsolète au cours des 6 prochains mois, mais j'espère que cela aidera quelqu'un dans l'intervalle.

Jamon Holmgren
la source
-1

_.get(obj, 'address.street.name')fonctionne très bien pour JavaScript où vous n'avez aucun type. Mais pour TypeScript, nous avons besoin du véritable opérateur Elvis!

Angelos Pikoulas
la source