Typescript: Suggestion : option pour inclure undefined dans les signatures d'index

Créé le 31 janv. 2017  ·  242Commentaires  ·  Source: microsoft/TypeScript

Mise à jour : pour ma dernière proposition voir le commentaire https://github.com/Microsoft/TypeScript/issues/13778#issuecomment -406316164

Avec strictNullChecks activé, TypeScript n'inclut pas undefined dans les signatures d'index (par exemple sur un objet ou un tableau). Il s'agit d'une mise en garde bien connue et discutée dans plusieurs numéros, à savoir https://github.com/Microsoft/TypeScript/issues/9235 , https://github.com/Microsoft/TypeScript/issues/13161 , https://github .com/Microsoft/TypeScript/issues/12287 et https://github.com/Microsoft/TypeScript/pull/7140#issuecomment-192606629 .

Exemple:

const xs: number[] = [1,2,3];
xs[100] // number, even with strictNullChecks

Cependant, il apparaît à la lecture des problèmes ci-dessus que de nombreux utilisateurs de TypeScript souhaitent que ce ne soit pas le cas. Certes, si les signatures d'index incluaient undefined , le code nécessitera probablement beaucoup plus de protection, mais - pour certains - c'est un compromis acceptable pour une sécurité de type accrue.

Exemple de signatures d'index incluant undefined :

const xs: number[] = [1,2,3];
xs[100] // number | undefined

J'aimerais savoir si ce comportement peut être considéré comme une option de compilateur supplémentaire en plus de strictNullChecks . De cette façon, nous sommes en mesure de satisfaire tous les groupes d'utilisateurs : ceux qui souhaitent des vérifications nulles strictes avec ou sans indéfini dans leurs signatures d'index.

Committed Suggestion

Commentaire le plus utile

Nous restons assez sceptiques quant au fait que quiconque tirerait profit de ce drapeau dans la pratique. Les cartes et les éléments similaires à des cartes peuvent déjà s'inscrire à | undefined sur leurs sites de définition

L'un des objectifs de TypeScript n'est-il pas de permettre aux erreurs d'être détectées au moment de la "compilation" plutôt que de compter sur l'utilisateur pour se souvenir/savoir faire quelque chose de spécifique ? Cela semble aller à l'encontre de cet objectif ; obligeant l'utilisateur à faire quelque chose afin d'éviter les plantages. La même chose pourrait être dite pour de nombreuses autres fonctionnalités ; ils ne sont pas nécessaires si le développeur fait toujours x. Le but de TypeScript est (vraisemblablement) de rendre le travail plus facile et d'éliminer ces choses.

Je suis tombé sur ce bogue parce que j'activais strictNullChecks sur du code existant et j'avais déjà une comparaison, j'ai donc eu l'erreur. Si j'avais écrit un tout nouveau code, je n'aurais probablement pas réalisé le problème ici (le système de types me disait que j'obtenais toujours une valeur) et je me suis retrouvé avec un échec d'exécution. Compter sur les développeurs de TS pour se rappeler (ou pire, même savoir) qu'ils sont censés déclarer toutes leurs cartes avec | undefined impression que TypeScript ne parvient pas à faire ce que les gens veulent réellement.

Tous les 242 commentaires

À l'exception de strictNullChecks, nous n'avons pas d'indicateurs qui modifient le comportement du système de type. Les indicateurs activent/désactivent généralement le rapport d'erreurs.
vous pouvez toujours avoir une version personnalisée de la bibliothèque qui définit tous les indexeurs avec | undefined . devrait fonctionner comme prévu.

@mhegazy C'est une idée intéressante. Des conseils sur la façon de remplacer les signatures de type pour le tableau/l'objet ?

Il y a interface Array<T> dans lib.d.ts . J'ai recherché par l'expression rationnelle \[\w+: (string|number)\] pour trouver également d'autres signatures d'indexation.

Intéressant, alors j'ai essayé ceci :

{
    // https://github.com/Microsoft/TypeScript/blob/1f92bacdc81e7ae6706ad8776121e1db986a8b27/lib/lib.d.ts#L1300
    declare global {
        interface Array<T> {
            [n: number]: T | undefined;
        }
    }

    const xs = [1,2,3]
    const x = xs[100]
    x // still number :-(
}

Des idées?

copiez lib.d.ts localement, disons lib.strict.d.ts , changez la signature d'index en [n: number]: T | undefined; , incluez le fichier dans votre compilation. vous devriez voir l'effet escompté.

Cool, merci pour ça.

Le problème avec le correctif suggéré ici est qu'il nécessite un fork et le maintien d'un fichier lib séparé.

Je me demande si cette fonctionnalité est suffisamment demandée pour justifier une sorte d'option prête à l'emploi.

En passant, il est intéressant de noter que la signature de type pour la méthode get sur les collections ES6 ( Map / Set ) renvoie T | undefined lorsque Array signatures d'index Object ne le font pas.

c'est une décision consciente. ce serait très ennuyeux que ce code soit une erreur :

var a = [];
for (var i =0; i< a.length; i++) {
    a[i]+=1; // a[i] is possibly undefined
}

et il serait déraisonnable de demander à chaque utilisateur d'utiliser ! . ou pour écrire

var a = [];
for (var i =0; i< a.length; i++) {
    if (a[i]) {
        a[i]+=1; // a[i] is possibly undefined
    }
}

Pour la carte ce n'est généralement pas le cas.

De même pour vos types, vous pouvez spécifier | undefined sur toutes vos signatures d'index, et vous obtiendrez le comportement attendu. mais pour Array ce n'est pas raisonnable. vous êtes invités à forker la bibliothèque et à apporter les modifications que vous devez faire, mais nous n'avons pas l'intention de modifier la déclaration dans la bibliothèque standard à ce stade.

Je ne pense pas que l'ajout d'un drapeau pour changer la forme d'une déclaration soit quelque chose que nous ferions.

@mhegazy mais pour les tableaux avec des trous a[i] est en fait peut-être indéfini :

let a: number[] = []
a[0] = 0
a[5] =0
for (let i = 0; i < a.length; i++) {
  console.log(a[i])
}

La sortie est :

undefined
undefined
undefined
undefined
0

Nous restons assez sceptiques quant au fait que quiconque tirerait profit de ce drapeau dans la pratique. Les cartes et les éléments similaires à des cartes peuvent déjà opter pour | undefined sur leurs sites de définition, et l'application d'un comportement de type CLUF sur l'accès aux tableaux ne semble pas être une victoire. Nous aurions probablement besoin d'améliorer considérablement le CFA et les gardes de type pour rendre cela acceptable.

Si quelqu'un veut modifier son lib.d.ts et corriger toutes les ruptures en aval dans son propre code et montrer à quoi ressemble la différence globale pour montrer que cela a une proposition de valeur, nous sommes ouverts à ces données. Alternativement, si beaucoup de gens sont vraiment enthousiastes à l'idée d'utiliser plus de ! postfix mais n'ont pas encore suffisamment d'opportunités pour le faire, ce drapeau serait une option.

Nous restons assez sceptiques quant au fait que quiconque tirerait profit de ce drapeau dans la pratique. Les cartes et les éléments similaires à des cartes peuvent déjà s'inscrire à | undefined sur leurs sites de définition

L'un des objectifs de TypeScript n'est-il pas de permettre aux erreurs d'être détectées au moment de la "compilation" plutôt que de compter sur l'utilisateur pour se souvenir/savoir faire quelque chose de spécifique ? Cela semble aller à l'encontre de cet objectif ; obligeant l'utilisateur à faire quelque chose afin d'éviter les plantages. La même chose pourrait être dite pour de nombreuses autres fonctionnalités ; ils ne sont pas nécessaires si le développeur fait toujours x. Le but de TypeScript est (vraisemblablement) de rendre le travail plus facile et d'éliminer ces choses.

Je suis tombé sur ce bogue parce que j'activais strictNullChecks sur du code existant et j'avais déjà une comparaison, j'ai donc eu l'erreur. Si j'avais écrit un tout nouveau code, je n'aurais probablement pas réalisé le problème ici (le système de types me disait que j'obtenais toujours une valeur) et je me suis retrouvé avec un échec d'exécution. Compter sur les développeurs de TS pour se rappeler (ou pire, même savoir) qu'ils sont censés déclarer toutes leurs cartes avec | undefined impression que TypeScript ne parvient pas à faire ce que les gens veulent réellement.

Nous restons assez sceptiques quant au fait que quiconque tirerait profit de ce drapeau dans la pratique. Les cartes et les éléments similaires à des cartes peuvent déjà s'inscrire | indéfini sur leurs sites de définition
L'un des objectifs de TypeScript n'est-il pas de permettre aux erreurs d'être détectées au moment de la "compilation" plutôt que de compter sur l'utilisateur pour se souvenir/savoir faire quelque chose de spécifique ?

En fait le but est :

1) Identifier de manière statique les constructions susceptibles d'être des erreurs.

Ce qui est discuté ici, c'est la probabilité d'une erreur (faible de l'avis de l'équipe TypeScript) et la convivialité productive commune du langage. Certains des premiers changements apportés au CFA ont été d'être moins alarmistes ou d'améliorer l'analyse du CFA pour déterminer plus intelligemment ces choses.

Je pense que la question de l'équipe TypeScript est qu'au lieu de discuter de sa stricte exactitude, de fournir des exemples où ce type de rigueur, dans l'usage courant, identifierait en fait une erreur contre laquelle il faut se prémunir.

Je suis entré un peu plus dans le raisonnement à ce commentaire https://github.com/Microsoft/TypeScript/issues/11238#issuecomment -250562397

Pensez aux deux types de clés dans le monde : celles dont vous savez qu'elles ont une propriété correspondante dans un objet (coffre-fort), celles dont vous ne savez

Vous obtenez le premier type de clé, une clé "sûre", en écrivant le code correct comme

for (let i = 0; i < arr.length; i++) {
  // arr[i] is T, not T | undefined

ou

for (const k of Object.keys(obj)) {
  // obj[k] is T, not T | undefined

Vous obtenez le deuxième type à partir de la clé, le type "dangereux", à partir d'éléments tels que les entrées utilisateur, les fichiers JSON aléatoires du disque, ou une liste de clés pouvant être présentes mais non.

Donc, si vous avez une clé dangereuse et que vous l'indexez, ce serait bien d'avoir | undefined ici. Mais la proposition n'est pas "Traiter les clés toutes les clés, même les plus sûres, comme dangereuses". Et une fois que vous commencez à considérer les clés sûres comme dangereuses, la vie est vraiment nulle. Vous écrivez du code comme

for (let i = 0; i < arr.length; i++) {
  console.log(arr[i].name);

et TypeScript se plaint que arr[i] pourrait être undefined même si bon, je viens de tester @#%#ing pour cela . Maintenant, vous prenez l'habitude d'écrire du code comme celui-ci, et cela semble stupide :

for (let i = 0; i < arr.length; i++) {
  // TypeScript makes me use ! with my arrays, sad.
  console.log(arr[i]!.name);

Ou peut-être écrivez-vous du code comme celui-ci :

function doSomething(myObj: T, yourObj: T) {
  for (const k of Object.keys(myObj)) {
    console.log(yourObj[k].name);
  }
}

et TypeScript dit "Hé, cette expression d'index pourrait être | undefined , donc vous la corrigez consciencieusement car vous avez déjà vu cette erreur 800 fois :

function doSomething(myObj: T, yourObj: T) {
  for (const k of Object.keys(myObj)) {
    console.log(yourObj[k]!.name); // Shut up TypeScript I know what I'm doing
  }
}

Mais vous n'avez pas corrigé le bug . Vous vouliez écrire Object.keys(yourObj) , ou peut-être myObj[k] . C'est le pire type d'erreur de compilation, car cela ne vous aide dans aucun scénario - il s'agit uniquement d'appliquer le même rituel à chaque type d'expression, sans se soucier du fait qu'il soit ou non plus dangereux que toute autre expression de la même forme.

Je pense à l'ancien « Êtes-vous sûr de vouloir supprimer ce fichier ? » dialogue. Si cette boîte de dialogue apparaissait à chaque fois que vous essayiez de supprimer un fichier, vous apprendriez très rapidement à frapper del y alors que vous aviez l'habitude d'appuyer sur del , et vos chances de ne pas supprimer quelque chose d'important sont réinitialisées -dialogue de base. Si au lieu de cela, la boîte de dialogue n'apparaissait que lorsque vous supprimiez des fichiers alors qu'ils n'allaient pas dans la corbeille de recyclage, vous disposez désormais d'une sécurité significative. Mais nous n'avons aucune idée (et nous ne le pourrions pas) si vos clés d'objet sont sûres ou non, donc l'affichage du message "Êtes-vous sûr de vouloir indexer cet objet ?" dialogue chaque fois que vous le faites, il est peu probable que vous trouviez des bogues à un meilleur taux que de ne pas tout afficher.

Identifiez de manière statique les constructions susceptibles d'être des erreurs.

Peut-être que cela doit être modifié pour dire « Identifiez statiquement les constructions qui sont plus susceptibles que d'autres d'être des erreurs ». : clin d'oeil:. Je me souviens quand nous obtenons des bogues qui sont essentiellement "J'ai utilisé * quand je voulais utiliser / , pouvez-vous utiliser make * un avertissement ?"

Je comprends, mais l'index hors plage est un problème réel et commun ; obliger les gens à énumérer les tableaux d'une manière qu'ils ne peuvent pas faire, ce ne serait pas une mauvaise chose.

Le correctif avec ! En fait, je n'aime pas non plus - et si quelqu'un se présente et fait un changement tel que l'hypothèse est maintenant invalide ? Vous êtes de retour à la case départ (un échec d'exécution potentiel pour quelque chose que le compilateur devrait attraper). Il devrait y avoir des moyens sûrs d'énumérer des tableaux qui ne reposent pas sur des mensonges sur les types ou sur l'utilisation de ! (par exemple, ne pouvez-vous pas faire quelque chose comme array.forEach(i => console.log(i.name) ?).

Vous affinez déjà les types en fonction du code, donc en théorie, ne pourriez-vous pas repérer les modèles qui sont sûrs affiner le type pour supprimer | undefined dans ces cas, en donnant le meilleur des deux mondes ? Je dirais que si vous ne pouvez pas facilement transmettre au compilateur que vous n'accédez pas à un élément valide, alors peut-être que votre garantie est invalide ou pourrait facilement être accidentellement rompue à l'avenir.

Cela dit, je n'utilise TS que sur un seul projet et celui-ci sera finalement migré vers Dart, il est donc peu probable que cela fasse une réelle différence pour moi. Je suis juste triste que la qualité générale du logiciel soit mauvaise et qu'il y ait une opportunité d'aider à éliminer les erreurs ici qui sont apparemment ignorées pour des raisons de commodité. Je suis sûr que le système de type pourrait être rendu solide et les désagréments courants résolus de manière à ne pas introduire ces trous.

Quoi qu'il en soit, ce ne sont que mes 2 cents.. Je ne veux pas traîner ça - je suis sûr que vous comprenez d'où nous venons et vous êtes bien mieux placé que moi pour prendre des décisions à ce sujet :-)

Je pense qu'il y a plusieurs choses à considérer. Il existe de nombreux modèles d'itération sur des tableaux d'usage courant qui prennent en compte le nombre d'éléments. Bien qu'il soit possible d'accéder de manière aléatoire aux index des tableaux, dans la nature, il s'agit d'un modèle très rare et n'est pas susceptible d'être une erreur statique. Bien qu'il existe des méthodes modernes d'itération, la plus courante serait quelque chose comme :

for (let i = 0; i < a.length; i++) {
  const value = a[i];
}

Si vous supposez que les tableaux de rechange sont rares (ils le sont), il est peu utile que value soit | undefined . S'il existe un modèle commun, dans la nature, où cela est risqué (et probablement une erreur), alors je pense que TypeScript écouterait pour considérer cela, mais les modèles qui sont généralement utilisés, doivent à nouveau contre toutes les valeurs d'un L'accès à l'index être indéfini est clairement quelque chose qui affecte la productivité et, comme indiqué, peut être choisi si vous êtes dans une situation où il est potentiellement utile.

Je pense qu'il y a déjà eu des discussions sur l'amélioration de CFA afin qu'il y ait un moyen d'exprimer la co-dépendance des valeurs (par exemple, Array.prototype.length se rapporte à la valeur de l'index) afin que des éléments comme l'index hors limites puissent être analysés de manière statique. De toute évidence, c'est un travail important, composé de toutes sortes de cas extrêmes et de considérations que je n'aimerais pas comprendre (bien qu'il soit probable qu'Anders se réveille avec des sueurs froides à cause de choses comme celle-ci).

Cela devient donc un compromis... Sans améliorations CFA, compliquez 90% du code avec des harengs rouges pour attraper potentiellement 10% de mauvais code . Sinon, il investit dans des améliorations majeures du CFA, qui pourraient avoir leurs propres conséquences en termes de stabilité et de problèmes à nouveau, trouvant ce qui serait un code dangereux.

Il y a tellement de choses que TypeScript peut faire pour nous sauver de nous-mêmes.

Tout cet accent est mis sur les tableaux et je suis d'accord que c'est moins susceptible d'être un problème là-bas, mais la plupart des problèmes initiaux soulevés (comme le mien) concernaient des cartes où je ne pense pas que le cas courant soit des clés toujours existantes?

Tout cet accent est mis sur les tableaux et je suis d'accord que c'est moins susceptible d'être un problème là-bas, mais la plupart des problèmes initiaux soulevés (comme le mien) concernaient des cartes où je ne pense pas que le cas courant soit des clés toujours existantes?

S'il s'agit de votre type, ajoutez | undefined à la signature d'index. C'est déjà une erreur d'indexer dans un type sans signature d'index sous --noImplicitAny .
ES6 Map est déjà défini avec get comme get(key: K): V | undefined; .

j'ai réécrit toutes les définitions des tableaux et des cartes pour que les signatures d'index retournent | undefined , jamais regretté depuis, trouvé quelques bugs, cela ne cause aucun inconfort car je travaille avec des tableaux indirectement via une bibliothèque à la main qui garde les contrôles pour indéfini ou ! intérieur

Ce serait formidable si TypeScript pouvait contrôler le flux des vérifications comme le fait C# (pour éliminer les vérifications de plage d'index pour économiser du temps processeur), par exemple :

declare var values: number[];
for (let index = 0, length = values.length; index< length; index ++) {
   const value = value[index]; // always defined, because index is within array range and only controlled by it
}

(à ceux qui utilisent des réseaux clairsemés - tuez-vous avec un feu brûlant)

comme pour Object.keys , il faut un type spécial disons allkeysof T pour laisser l'analyse du flux de contrôle effectuer des rétrécissements sûrs

Je pense que ce serait une bonne option, car pour le moment, nous mentons essentiellement sur le type d'opération d'indexation, et il peut être facile d'oublier d'ajouter | undefined à mes types d'objets. Je pense que l'ajout de ! dans les cas où nous savons que nous voulons ignorer undefined s serait un bon moyen de gérer les opérations d'indexation lorsque cette option est activée.

Il y a (au moins) deux autres problèmes à mettre |undefined dans vos définitions de type d'objet :

  • cela signifie que vous pouvez affecter undefined dans ces objets, ce qui n'est certainement pas prévu
  • d'autres opérations, comme Object.values (ou _.values ) vous obligeront à gérer undefined dans les résultats

tslint signale un avertissement faussement positif de condition constante, car le script renvoie des informations de type erronées (= manquant de | undefined ).
https://github.com/palantir/tslint/issues/2944

L'une des erreurs régulièrement ignorées avec l'absence de | undefined dans l'indexation du tableau est ce modèle lorsqu'il est utilisé à la place de find :

const array = [ 1, 2, 3 ];
const firstFour = array.filter((x) => (x === 4))[0];
// if there is no `4` in the `array`,
// `firstFour` will be `undefined`, but TypeScript thinks `number` because of the indexer signature.
const array = [ 1, 2, 3 ];
const firstFour = array.find((x) => (x === 4));
// `firstFour` will be correctly typed as `number | undefined` because of the `find` signature.

J'utiliserais certainement ce drapeau. Oui, les anciennes boucles for seront ennuyeuses à utiliser, mais nous avons l'opérateur ! pour dire au compilateur quand nous savons qu'il est défini :

for (let i = 0; i < arr.length; i++) {
  foo(arr[i]!)
}

De plus, ce problème n'est pas un problème avec les boucles for of plus récentes et bien meilleures et il existe même une règle TSLint prefer-for-of qui vous dit de ne pas utiliser les boucles for ancienne plus.

Actuellement, j'ai l'impression que le système de types est incohérent pour le développeur. array.pop() nécessite un if ou une assertion ! , mais l'accès via [array.length - 1] ne le fait pas. ES6 map.get() requiert une vérification if ou une assertion ! , mais pas un hachage d'objet. L'exemple de @sompylasar est également bon.

Un autre exemple est la déstructuration :

const specifier = 'Microsoft/TypeScript'
const [repo, revision] = specifier.split('@') // types of repo and revision are string
console.log('Repo: ' + repo)
console.log('Short rev: ' + revision.slice(0, 7)) // Error: Cannot call function 'slice' on undefined

J'aurais préféré que le compilateur m'oblige à faire ceci :

const specifier = 'Microsoft/TypeScript'
const [repo, revision] = specifier.split('@') // types of repo and revision are string | undefined
console.log('Repo: ', repo || 'no repo')
console.log('Short rev:', revision ? revision.slice(0, 7) : 'no revision')

Ce sont des bogues réels que j'ai vus qui auraient pu être évités par le compilateur.

Imo, cela ne devrait pas appartenir aux fichiers de saisie, mais devrait plutôt être un mécanisme de système de type - lorsque vous accédez à quelque chose avec une signature d'index, cela peut être undefined . Si votre logique garantit que ce n'est pas le cas, utilisez simplement ! . Sinon ajoutez un if et c'est bon.

Je pense que beaucoup de gens préféreraient que le compilateur soit strict avec quelques assertions nécessaires plutôt que d'être lâche avec des bogues non détectés.

J'aimerais vraiment voir ce drapeau ajouté. Dans la base de code de mon entreprise, l'accès aléatoire aux tableaux est la rare exception et les boucles for sont des odeurs de code que nous voudrions généralement réécrire avec des fonctions d'ordre supérieur.

@pelotom quelle est votre préoccupation alors (puisqu'il semble que vous vous êtes surtout tiré d'affaire) ?

@aleksey-bykov principalement des signatures d'index d'objets, qui se produisent abondamment dans les bibliothèques tierces. Je voudrais accéder à une propriété sur { [k: string]: A } pour m'avertir que le résultat est peut-être indéfini. J'ai seulement mentionné l'indexation des tableaux parce qu'elle a été évoquée pour expliquer pourquoi le drapeau serait trop ennuyeux à utiliser.

tu sais que tu peux les réécrire exactement comme tu veux ? (avec un peu de travail supplémentaire)

Oui, je pourrais réécrire les frappes de chacun pour eux, ou je pourrais activer un drapeau du compilateur 😜

continuez à jouer au capitaine O... : vous pouvez réécrire votre lib.d.ts aujourd'hui et être l'heureux propriétaire d'une base de code plus solide ou vous pouvez attendre le drapeau pour les N prochaines années

@aleksey-bykov comment faire en réécrivant lib.d.ts ?

declare type Keyed<T> = { [key: string]: T | undefined; }

puis dans la définition Array dans lib.es2015.core.d.ts , remplacez

[n: number]: T;

avec

[n: number]: T | undefined;

@aleksey-bykov vous avez peut-être manqué la partie où j'ai dit que je ne me souciais pas des tableaux. Je me soucie de l'endroit où les bibliothèques tierces ont déclaré que quelque chose était de type { [k: string]: T } , et je veux accéder à un tel objet pour renvoyer quelque chose de possiblement indéfini. Il n'y a aucun moyen d'y parvenir en éditant simplement lib.d.ts ; cela nécessite de changer les signatures de la bibliothèque en question.

contrôlez-vous les fichiers de définition tiers ? si oui tu peux les réparer

Et maintenant nous revenons à

Oui, je pourrais réécrire les frappes de chacun pour eux, ou je pourrais activer un drapeau du compilateur 😜

Le temps est un cercle plat.

ne sois pas stupide, tu n'utilises pas "les frappes de tout le monde", n'est-ce pas ? c'est littéralement une journée de travail max pour un projet typique, j'y ai été fait

Oui, je dois modifier les frappes des autres tout le temps et j'aimerais le faire moins.

et tu le feras dans N ans, peut-être, pour l'instant tu peux souffrir ou te débrouiller

Merci pour votre contribution incroyablement constructive 👍

la contribution constructive à cela est la suivante, ce problème doit être clos, car :
une. soit la décision de savoir si [x] peut être undefined ou non est laissée aux développeurs par

  • les laisser tout garder dans leur tête comme ils le faisaient toujours avant
  • ou en modifiant lib.d.ts et 3rd-party.d.ts comme suggéré

b. ou il faut une syntaxe spéciale / des types / une analyse de flux / N ans pour atténuer quelque chose qui peut être facilement fait à la main dans #a

Le problème est une proposition pour (b), sauf qu'aucune nouvelle syntaxe n'est proposée, c'est juste un indicateur de compilateur.

En résumé, le type { [x: string]: {} } est presque toujours un mensonge ; à l'exception de l'utilisation de Proxy , aucun objet ne peut avoir un nombre infini de propriétés, encore moins toutes les chaînes possibles. La proposition est d'avoir un indicateur de compilateur qui le reconnaisse. Il se peut que ce soit trop difficile à mettre en œuvre pour ce qui est gagné ; Je laisse cet appel aux réalisateurs.

le fait est que ni l'un ni l'autre

  • T | undefined
  • ni T

est bon pour le cas général

afin de faire les choses correctement pour le cas général, vous devez encoder les informations sur la préséance des valeurs dans les types de leurs conteneurs, ce qui appelle un système de types dépendants ... ce qui en soi n'est pas une mauvaise chose à avoir :) mais pourrait être aussi complexe que tout le système de type tapuscrit actuel fait à ce jour, pour le plaisir de ... vous épargner quelques modifications?

T | undefined est correct pour le cas général, pour les raisons que je viens de donner. Je vais ignorer vos divagations absurdes sur les types dépendants, passez une bonne journée.

tu peux m'ignorer autant que tu veux mais T | undefined est un dépassement pour

declare var items: number[];
for (var index = 0; index < items.length; index ++) {
   void items[index];
}

Je préfère avoir T | undefined par défaut et dire au compilateur que index est une plage d'index numérique de items donc ne sort pas si les limites sont appliquées à items ; dans les cas simples tels qu'un ensemble de formes de boucle for / while fréquemment utilisées, le compilateur pourrait déduire cela automatiquement ; dans les cas complexes, désolé, il peut y avoir des undefined s. Et oui, les types basés sur la valeur conviendraient bien ici ; les types de chaîne littéraux sont si utiles, pourquoi ne pas avoir des types booléens littéraux et nombre et plage/ensemble de plages ? En ce qui concerne TypeScript, il essaie de couvrir tout ce qui peut être exprimé avec JavaScript (contrairement à, par exemple, Elm qui limite cela).

c'est littéralement une journée de travail max pour un projet typique, j'y ai été fait

@aleksey-bykov, curieux de savoir quelle a été votre expérience après ce changement ? à quelle fréquence devez-vous utiliser ! ? et à quelle fréquence trouvez-vous le compilateur signalant des bogues réels ?

@mhegazy honnêtement, je n'ai pas remarqué beaucoup de différence en passant de T à T | undefined , je n'ai pas non plus attrapé de bugs, je suppose que mon problème est que je travaille avec des tableaux via des fonctions utilitaires qui gardent ! en eux, donc littéralement il n'y avait aucun effet pour le code extérieur :

https://github.com/aleksey-bykov/basic/blob/master/array.ts

Dans quel fichier lib puis-je trouver la définition du type d'index pour les objets ? J'ai localisé et mis Array jour [n: number]: T à [n: number]: T | undefined . Maintenant, je voudrais faire la même chose pour les objets.

il n'y a pas d'interface standard (comme Array pour les tableaux) pour les objets avec la signature d'index, vous devez rechercher des définitions exactes pour chaque cas dans votre code et les corriger

vous devez rechercher des définitions exactes pour chaque cas dans votre code et les corriger

Que diriez-vous d'une recherche de clé directe ? Par exemple

const xs = { foo: 'bar' }
xs['foo']

Existe-t-il un moyen d'appliquer T | undefined au lieu de T ici ? Actuellement, j'utilise ces assistants partout dans ma base de code, comme alternatives sûres de type aux recherches d'index sur les tableaux et les objets :

// TS doesn't return the correct type for array and object index signatures. It returns `T` instead
// of `T | undefined`. These helpers give us the correct type.
// https://github.com/Microsoft/TypeScript/issues/13778
export const getIndex = function<X> (index: number, xs: X[]): X | undefined {
  return xs[index];
};
export const getKeyInMap = function<X> (key: string, xs: { [key: string]: X }): X | undefined {
  return xs[key];
};

@mhegazy Au moment où j'écris ceci, je https://unsplash.com qui aurait pu être détecté avec des types de signature d'index plus stricts.

Je vois, considérons l'opérateur de type mappé :

const xs = { foo: 'bar' };
type EachUndefined<T> = { [P in keyof T]: T[P] | undefined; }
const xu : EachUndefined<typeof xs> = xs;
xu.foo; // <-- string | undefined

Si un indicateur comme --strictArrayIndex n'est pas une option, car les indicateurs ne sont pas conçus pour modifier les fichiers lib.d.ts . Peut-être que vous pouvez publier des versions strictes de fichiers lib.d.ts comme " lib": ['strict-es6'] ?

Il pourrait contenir plusieurs améliorations, pas seulement un index de tableau strict. Par exemple, Object.keys :

interface ObjectConstructor {
    // ...
    keys(o: {}): string[];
}

Pourrait être:

interface ObjectConstructor {
    // ...
    keys<T>(o: T): (keyof T)[];
}

Mise à jour du SBS d'aujourd'hui : nous nous sommes criés dessus pendant 30 minutes et il ne s'est rien passé. 🤷‍♂️

@RyanCavanaugh Qu'est-ce qu'un SBS, par curiosité ?

@radix "

c'est intriguant, car la solution est évidente:
T et T | undefined sont tous deux faux, la seule bonne façon est de rendre la variable d'index consciente de la capacité de son conteneur, soit en la choisissant dans un ensemble, soit en l'enfermant dans une plage numérique connue

@RyanCavanaugh J'y ai pensé, il semble que l'astuce suivante couvrirait 87% de tous les cas :

  • values[index] donne T si index déclaré dans for (HERE; ...)
  • values[somethingElse] donne T | undefined pour toutes les variables déclarées en dehors de for

@aleksey-bykov nous avons discuté de quelque chose d'encore plus intelligent - qu'il pourrait y avoir un mécanisme de garde de type réel pour " arr[index] été testé par index < arr.length . Mais cela n'aiderait pas le cas où vous pop 'd au milieu d'une boucle, ou passé votre tableau à une fonction qui en a supprimé des éléments. Il ne semble vraiment pas que les gens recherchent un mécanisme qui empêche les erreurs OOB 82,9% du temps - après tout , il est déjà vrai qu'environ cette fraction de code d'indexation de tableau est correcte de toute façon. Ajouter une cérémonie pour corriger le code sans détecter de bogues dans les cas incorrects est un mauvais résultat.

ne pas impliquer qu'Aleksey muterait jamais un tableau

J'ai récemment porté une application d'Elm vers Typescript et les opérations d'indexation mal saisies sont probablement l'une des plus grandes sources de bogues que j'ai rencontrées, avec tous les paramètres les plus stricts de TS activés (également des trucs comme this non liés ).

  • vous ne pouvez pas suivre les mutations dans le conteneur
  • vous ne pouvez pas suivre les manipulations d'index

Cela dit, vous ne pouvez pas garantir, si oui, pourquoi essayeriez-vous même si un cas d'utilisation typique est une itération stupide à travers tous les éléments dans < array.length

comme je l'ai dit d'habitude

  • s'il y a une erreur, elle provient probablement de quelque part dans les clauses de l'instruction for : initialisation, incrémentation, condition d'arrêt et n'est donc pas quelque chose que vous pouvez vérifier, car cela nécessite une vérification de type complète
  • en dehors des clauses for il n'y a généralement pas de place pour l'erreur

donc tant que l'index est déclaré d'une manière ou d'une

Mais cela n'aiderait pas le cas où vous feriez apparaître une boucle au milieu ou transfériez votre tableau à une fonction qui en supprimait des éléments

Cela semble être un argument vraiment faible contre cela. Ces cas sont déjà cassés - les utiliser comme excuse pour ne pas réparer d'autres cas n'a aucun sens. Personne ici ne demande que l'un ou l'autre de ces cas soit traité correctement ni qu'il soit correct à 100 %. Nous voulons juste des types précis dans un cas vraiment courant. Il existe de nombreux cas dans TypeScript qui ne sont pas parfaitement gérés ; si vous faites quelque chose de bizarre, les types peuvent être erronés. Dans ce cas, nous ne faisons rien de bizarre et les types sont faux.

Ajout d'une cérémonie pour corriger le code

Je suis curieux de voir un exemple de code où il s'agit "d'ajouter une cérémonie pour corriger le code". Si je comprends bien, une boucle for de base sur un tableau est facile à détecter/réduire. Quel est le code du monde réel qui n'est pas une simple boucle for où cela devient gênant et qui n'est pas potentiellement un bogue ? (soit maintenant, soit pourrait provenir d'un changement trivial dans le futur). Je ne dis pas qu'il n'y en a pas; Je ne peux tout simplement pas l'imaginer et je n'ai vu aucun exemple (j'ai vu beaucoup d'exemples utilisant des boucles for, mais à moins que vous ne disiez qu'elles ne peuvent pas être réduites, elles semblent hors de propos).

sans attraper de bugs

Il y a eu des exemples de code de travail qui ne parvient pas à se compiler à cause de cela et de code qui se lance à l'exécution parce que les types induisent le développeur en erreur. Dire qu'il n'y a aucune valeur à soutenir cela est un non-sens.

Pourquoi ne pouvons-nous pas simplement garder les choses simples et ne pas ajouter du tout de comportement magique autour des boucles for à l'ancienne. Vous pouvez toujours utiliser ! pour faire fonctionner les choses. Si votre base de code est pleine de boucles for ancienne, n'utilisez pas le drapeau. Toutes les bases de code modernes avec lesquelles j'ai travaillé utilisent soit forEach ou for of pour itérer les tableaux et ces bases de code bénéficieraient de la sécurité de type supplémentaire.

nous ne faisons rien de bizarre et les types sont faux.

Ce paragraphe, pour moi, se lit comme une bonne raison de ne pas avoir cette fonctionnalité. Accéder à un tableau hors limites est une chose étrange à faire ; les types ne sont "mauvais" que si vous faites une chose peu commune (OOBing). La grande majorité de la lecture de code à partir d'un tableau le fait dans les limites ; il serait "mal" d'inclure undefined dans ces cas par cet argument.

... code de travail qui ne parvient pas à compiler

Je n'en connais aucun - pouvez-vous les indiquer spécifiquement ?

Pourquoi ne pouvons-nous pas simplement garder les choses simples et ne pas ajouter du tout de comportement magique autour des boucles for à l'ancienne. Vous pouvez toujours utiliser ! pour que les choses fonctionnent.

Quelle est la différence utile entre cela et une règle TSLint qui dit "Toutes les expressions d'accès au tableau doivent avoir un ! " ?

@RyanCavanaugh, mon hypothèse est que la règle TSLint ne serait pas en mesure de restreindre les types ou d'utiliser l'analyse de type de flux de contrôle (par exemple, envelopper l'accès dans un if , lancer une exception, return ing ou continue ing s'il n'est pas défini, etc.). Il ne s'agit pas seulement d'expressions d'accès aux tableaux, il s'agit également de déstructuration. À quoi ressemblerait l'implémentation pour l'exemple dans https://github.com/Microsoft/TypeScript/issues/13778#issuecomment -336265143 ? Pour appliquer le ! , il faudrait essentiellement créer une table pour suivre les types de ces variables comme étant éventuellement undefined . Ce qui me semble être quelque chose que le vérificateur de type devrait faire, pas un linter.

@RyanCavanaugh

Ce paragraphe, pour moi, se lit comme une bonne raison de ne pas avoir cette fonctionnalité. Accéder à un tableau hors limites est une chose étrange à faire ; les types ne sont "mauvais" que si vous faites une chose peu commune (OOBing).

... code de travail qui ne parvient pas à compiler

Je n'en connais aucun - pouvez-vous les indiquer spécifiquement ?

Dans mon cas, ce n'était pas un tableau, mais il a été fermé en tant que dupe, donc je suppose que ce problème est censé les couvrir également. Voir #11186 pour mon numéro d'origine. J'étais en train d'analyser un fichier dans une carte, puis de le comparer à undefined . IIRC J'ai eu l'erreur sur la comparaison qu'ils ne peuvent pas être indéfinis, même s'ils le pouvaient (et l'étaient).

Il est toujours permis de comparer quelque chose à undefined

ce qui est dommage

const canWeDoIt = null === undefined; // yes we can!

Il est toujours permis de comparer quelque chose à undefined

Cela fait si longtemps, j'ai peur de ne pas me souvenir exactement de l'erreur. J'ai définitivement eu une erreur pour le code qui fonctionnait bien (sans strictNullChecks ) et cela m'a conduit au test qui a abouti au cas ci-dessus.

Si j'ai un peu de temps, je verrai si je peux comprendre exactement ce que c'était encore. C'était certainement lié à cette frappe.

La majeure partie de cette discussion a été axée sur les tableaux, mais, d'après mon expérience, l'accès aux objets est l'endroit où cette fonctionnalité serait la plus utile. Par exemple, cet exemple (simplifié) de ma base de code semble raisonnable et se compile bien, mais c'est certainement un bogue :

export type Chooser = (context?: Context) => number | string;
export interface Choices {
    [choice: number]: Struct;
    [choice: string]: Struct;
}

export const Branch = (chooser: Chooser, choices: Choices, context?: Context): Struct => {
    return choices[chooser(context)];  // Could be undefined
}

Concernant les objets et simplement changer la signature pour inclure | undefined , @types/node fait pour process.env :

    export interface ProcessEnv {
        [key: string]: string | undefined;
    }

mais cela ne permet pas du tout de restreindre le type :

process.env.SOME_CONFIG && JSON.parse(process.env.SOME_CONFIG)

donne

[ts]
Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
  Type 'undefined' is not assignable to type 'string'.

@felixfbecker

Je pense que cela est lié à ce bogue : https://github.com/Microsoft/TypeScript/issues/17960

Vous pouvez contourner ce problème en affectant l'env var à une variable, puis en protégeant cela :

const foo = process.env.SOME_CONFIG
foo && JSON.parse(foo);

L'ajout du | undefined dans certains types de nœuds a été fait pour permettre l'augmentation de ces interfaces pour plus de commodité avec les propriétés fréquemment utilisées pour obtenir la complétion du code. ch

interface ProcessEnv {
  foo?: string;
  bar?: string;
}

Sans | undefined dans la signature d'index, TSC se plaint avec Property 'foo' of type 'string | undefined' is not assignable to string index type 'string'. .

Je sais que cela a un coût si vous devez travailler avec des types ayant les | undefined supplémentaires et ceux qui n'en ont pas.

Ce serait vraiment bien si nous pouvions nous en débarrasser.

J'essaie de rassembler mes idées sur cette question.

Les objets sont un sac mélangé en JavaScript. Ils peuvent être utilisés à deux fins :

  • dictionnaires aka cartes, où les clés sont inconnues
  • enregistrements, où les clés sont connues

Pour les objets utilisés comme enregistrements, les signatures d'index n'ont pas besoin d'être utilisées. Au lieu de cela, les clés connues sont définies sur le type d'interface. Les recherches de clé valides renvoient le type correspondant, les recherches de clé non valides reviendront à la signature d'index. S'il n'y a pas de signature d'index définie (ce qui ne devrait pas être le cas pour les objets utilisés comme enregistrements) et que noImplicitAny est activé, cela générera une erreur comme souhaité.

Pour les objets utilisés comme dictionnaires (alias cartes) et tableaux, des signatures d'index sont utilisées, et nous pouvons choisir d'inclure | undefined dans le type de valeur. Par exemple, { [key: index]: string | undefined } . Toutes les recherches de clés sont valides (car les clés ne sont pas connues au moment de la compilation) et toutes les clés renvoient le même type (dans cet exemple, T | undefined ).

Étant donné que les signatures d'index ne doivent être utilisées que pour le modèle et les tableaux d'objets de dictionnaire, il est souhaitable que TypeScript applique | undefined dans le type de valeur de signature d'index : si les clés ne sont pas connues et que les recherches de clé renvoient éventuellement undefined .

Il existe de bons exemples de bogues qui peuvent apparaître invisibles sans cela, tels que Array.prototype.find renvoyant undefined , ou des recherches clés telles que array[0] renvoyant undefined . (La déstructuration n'est qu'une syntaxe de sucre pour les recherches de clé.) Il est possible d'écrire des fonctions comme getKey pour corriger le type de retour, mais nous devons nous fier à la discipline pour imposer l'utilisation de ces fonctions dans une base de code.

Si je comprends bien, le problème concerne alors la réflexion sur les objets et les tableaux de dictionnaire, de sorte que lors du mappage sur les clés d'un objet ou d'un tableau de dictionnaire, les recherches de clé sont connues pour être valides, c'est-à-dire qu'elles ne retourneront pas undefined . Dans ce cas, il ne serait pas souhaitable que les types de valeur incluent undefined . Il peut être possible d'utiliser l'analyse de flux de contrôle pour résoudre ce problème.

Est-ce la question en suspens, ou est-ce que je comprends mal?

Quels cas d'utilisation et problèmes n'ai-je pas mentionnés ?

Il y a (au moins) deux autres problèmes à mettre |undefined dans vos définitions de type d'objet :

  • cela signifie que vous pouvez affecter undefined dans ces objets, ce qui n'est certainement pas prévu
  • d'autres opérations, comme Object.values ​​(ou _.values) vous obligeront à gérer undefined dans les résultats

Je pense que c'est un point très important.

En ce moment, j'expérimente l'approche suivante :

Définir const safelyAccessProperty = <T, K extends keyof T>(object: T, key: K): T[K] | undefined => object[key];

Accédez ensuite à des propriétés telles que safelyAccessProperty(myObject, myKey) , au lieu de myObject[myKey] .

@plul Bonne prise. La discussion se concentre actuellement sur les opérations de lecture, mais la définition du type d'indexeur est en fait double, et l'ajout de | undefined permettrait d'écrire des valeurs undefined .

La fonction safelyAccessProperty vous expérimentez ( mentionnée ci-dessus comme getKey par @OliverJAsh) nécessite de la discipline et/ou une règle linter pour interdire les opérations d'indexation sur tous les tableaux et objets.

Cela peut être rendu évolutif si la fonction est fournie sur toutes les instances de tableau et d'objet (chaque type qui fournit des opérations d'indexation), comme en C++ std::vector a .at() qui lève une exception dans l'exécution pour l'accès OOB , et un opérateur [] non contrôlé qui, dans le meilleur des cas, plante avec SEGFAULT sur l'accès OOB, dans le pire des cas, corrompt la mémoire.

Je pense que le problème d'accès OOB n'est pas résoluble dans TypeScript/JavaScript au niveau de la définition de type uniquement, et nécessite la prise en charge du langage pour restreindre les opérations d'indexation potentiellement dangereuses si cette fonctionnalité de rigueur est activée.

La nature double de l'indexeur pourrait être modélisée en tant que propriété avec des opérations get et set en tant que fonctions, mais ce serait un changement radical pour toutes les définitions de type d'indexeur existantes.

Une idée qui semble prometteuse : et si vous pouviez utiliser ?: lors de la définition d'une signature d'index pour indiquer que vous vous attendez à ce que des valeurs manquent parfois dans des conditions normales d'utilisation ? Il agirait comme | undefined mentionné ci-dessus, mais sans les inconvénients gênants. Il faudrait interdire les valeurs explicites undefined , ce qui, je suppose, est une différence par rapport aux ?: habituels.

Cela ressemblerait à ceci :

type NewWay = {[key: string]?: string};
const n: NewWay = {};

// Has type string | undefined
n['foo']

// Has type Array<string>
Object.values(n)

// Doesn't work
n['foo'] = undefined;

// Works
delete n['foo'];

Par rapport à l'approche précédente de | undefined :

type OldWay = {[key: string]: string | undefined};
const o: OldWay = {};

// Has type string | undefined
o['foo']

// Has type Array<string | undefined>
Object.values(o)

// Works
o['foo'] = undefined;

// Works
delete o['foo'];

Je suis venu ici en rejetant l'ajout de | undefined dans le DT PR ci-dessus, car cela briserait tous les utilisateurs existants de cette API - cela pourrait-il être mieux considéré comme permettant à l'utilisateur de choisir à quel point il veut être pointilleux, plutôt que la bibliothèque ?


Je noterai que les propriétés facultatives ajoutent également le | undefined , et cela m'a mordu plusieurs fois - essentiellement, TS ne fait pas la distinction entre une propriété manquante et une propriété définie sur undefined. Je voudrais juste que { foo?: T, bar?: T } soit traité de la même manière que { [name: 'foo' | 'bar']: T } , quel que soit le sens (voir aussi les commentaires process.env ci-dessus)


TS est-il contre la rupture de la symétrie ici sur les indexeurs de nombres et de chaînes ?

foo[bar] && foo[bar].baz() est un modèle JS très courant, il semble maladroit lorsqu'il n'est pas pris en charge par TS (dans le sens de vous rappeler que vous devez le faire si vous n'ajoutez pas | undefined , et d'avertissement quand c'est évidemment pas nécessaire si vous le faites).


En ce qui concerne la mutation des tableaux pendant l'itération en cas de rupture de la garantie d'expression de garde, cela est également possible avec les autres gardes :

class Foo {
    foo: string | number = 123

    bar() {
        this.foo = 'bar'
    }

    broken() {
        if (typeof this.foo === 'number') {
            this.bar();
            this.foo.toLowerCase(); // right, but type error
            this.foo.toExponential(); // wrong, but typechecks
        }
    }
}

mais je suppose que c'est beaucoup moins probable dans le code réel que les anciennes boucles muter l'itéré.

Il est clair qu'il existe une demande pour cette fonctionnalité. J'espère vraiment que l'équipe TS trouvera une solution. Pas simplement en ajoutant | undefined à l'indexeur car il a ses propres problèmes (déjà mentionnés) mais de manière plus "intelligente" (la lecture renvoie T|undefined , l'écriture nécessite T , bon compilateur vérifier la boucle for , etc. une bonne proposition a également déjà été mentionnée.)

Nous acceptons les erreurs d'exécution lorsque nous mutons et travaillons avec des tableaux de manière non triviale, difficile à vérifier à l'aide du compilateur. Nous voulons juste vérifier les erreurs dans la plupart des cas et nous pouvons parfois utiliser ! .

Cela dit, si cette gestion plus stricte des tableaux était implémentée, maintenant avec #24897, il serait possible d'implémenter un rétrécissement de type agréable lors de la vérification de la longueur du tableau avec constante. Nous pourrions simplement réduire le tableau à tuple avec l'élément de repos.

let arr!: string[];
if (arr.length == 3) {
  //arr is of type [string, string, string]
}

if (arr.length > 3) {
  //arr is of type [string, string, string, string, ...string[]]
}

if (arr.length) {
  //arr is of type [string, ...string[]]
}

if (arr.length < 3) {
  //arr is of type [string?, string?, string?]
  if (arr.length > 0) {
    //arr is of type [string, string?, string?]
  }
}

Ce serait utile lorsque vous indexez par constante ou déstructurez un tableau.

let someNumber = 55;
if (arr.length) {
  let el1 = arr[0]; //string
  let el2 = arr[1]; //string | undefined
  let el3 = arr[someNumber]; //string | undefined
}

if(arr.length >= 3){
    let [el1, el2, el3, el4] = arr;
    //el1, el2, el3 are string
    // el4 is string | undefined    
}

if (arr.length == 2){
    let [el1, el2, el3] = arr; //compiler error: "Tuple type '[string, string]' with length '2' cannot be assigned to tuple with length '3'.",
}

Une autre question est que ferions-nous avec de grands nombres comme :

if(arr.length >= 99999){
    // arr is [string, string, ... , string, ...string[]]
}

Nous ne pouvons pas montrer le type de cet énorme tuple dans les messages IDE ou du compilateur.

Je suppose que nous pourrions avoir une syntaxe pour représenter "un tuple d'une certaine longueur avec le même type pour tous les éléments". Ainsi, par exemple, le tuple de 1000 chaînes est string[10000] et le type de tableau rétréci de l'exemple ci-dessus pourrait être [...string[99999], ...string[]] .

L'autre préoccupation est de savoir si l'infrastructure du compilateur peut prendre en charge de tels tuples maintenant, et sinon, à quel point cela serait-il difficile à faire.

Objets

Je veux toujours un type d'index de [key: string (or number, symbol)]: V | undefined , mais parfois j'oublie le cas undefined . Chaque fois qu'un développeur doit dire explicitement au compilateur "faites-moi confiance, c'est le genre de chose pour de vrai", vous savez que c'est dangereux.
Cela a très peu de sens de taper Map.get correctement (strictement) mais les objets simples obtiennent un laissez-passer gratuit.
Pourtant, cela est facilement réparable dans le pays des utilisateurs, donc ce n'est pas trop mal. Je n'ai pas de solution en tout cas.

Tableaux

Il me manque peut-être quelque chose, mais il semble que l'argument selon lequel "vous n'accédez presque jamais à un tableau de manière dangereuse" puisse aller dans les deux sens, en particulier avec un nouveau drapeau de compilateur.

J'ai tendance à penser que de plus en plus de gens suivent ces deux bonnes pratiques :

  • Utilisez des méthodes ou des bibliothèques natives fonctionnelles pour itérer ou transformer des tableaux. Pas d'accès au support ici.
  • Ne pas muter les tableaux en place

Dans cet esprit, les seuls cas restants et rares où vous avez besoin d'une logique d'accès aux supports de bas niveau bénéficieraient vraiment de la sécurité de type.

Celui-ci semble être une évidence et je ne pense pas que copier-coller l'intégralité de lib.d.ts localement soit une solution de contournement acceptable.

Lorsque nous indexons explicitement dans un tableau/objet, par exemple pour obtenir le premier élément d'un tableau ( array[0] ), nous voulons que le résultat inclue undefined .

Ceci est possible en ajoutant undefined à la signature d'index.

Cependant, si je comprends bien, le problème avec l'inclusion de undefined dans la signature d'index est que, lors du mappage sur le tableau ou l'objet, les valeurs incluront undefined même si elles sont connues pour ne pas être undefined (sinon nous ne ferions pas de mappage sur eux).

Les types/signatures d'index sont utilisés à la fois pour les recherches d'index (par exemple, array[0] ) et le mappage (par exemple, les boucles for et Array.prototype.map ), mais nous avons besoin de types différents pour chacun de ces cas.

Ce n'est pas un problème avec Map car Map.get est une fonction, et donc son type de retour peut être séparé du type de valeur interne, contrairement à l'indexation dans un tableau/objet qui n'est pas une fonction, et utilise donc directement la signature d'index.

Alors, la question devient : comment pouvons-nous satisfaire les deux cas ?

// Manually adding `undefined` to the index signature
declare const array: (number | undefined)[];

const first = array[0]; // number | undefined, as desired :-)
type IndexValue = typeof array[0]; // number | undefined, as desired! :-)

array.map(x => {
  x // number | undefined, not desired! :-(
})

Proposition

Une option de compilateur qui traite les recherches d'index (par exemple array[0] ) de la même manière que Set.get et Map.get sont tapés, en incluant undefined dans le type de valeur d'index (pas la signature d'index elle-même). La signature d'index réelle elle-même n'inclurait pas undefined , de sorte que les fonctions de mappage ne sont pas affectées.

Exemple:

declare const array: number[];

// The compiler option would include `undefined` in the index value type
const first = array[0]; // number | undefined, as desired :-)
type IndexValue = typeof array[0]; // number | undefined, as desired :-)

array.map(x => {
  x // number, as desired :-)
})

Cependant, cela ne résoudra pas le cas de boucle sur un tableau/des objets à l'aide d'une boucle for , car cette technique utilise des recherches d'index.

for (let i = 0; i < array.length; i++) {
  const x = array[i];
  x; // number | undefined, not desired! :-(
}

Pour moi et pour beaucoup d'autres, cela est acceptable car les boucles for ne sont pas utilisées, préférant plutôt utiliser un style fonctionnel, par exemple Array.prototype.map . Si nous devions les utiliser, nous serions heureux d'utiliser l'option de compilation suggérée ici avec des opérateurs d'assertion non nuls.

for (let i = 0; i < array.length; i++) {
  const x = array[i]!;
  x; // number, as desired :-)
}

Nous pourrions également fournir un moyen d'accepter ou de refuser, par exemple avec une syntaxe pour décorer la signature de l'index (veuillez pardonner la syntaxe ambiguë que j'ai trouvée pour l'exemple). Cette syntaxe serait juste un moyen de signaler le comportement que nous voulons pour les recherches d'index.

Désinscription (l'option du compilateur est activée par défaut, désinscription si nécessaire) :

declare const array: { [index: number]!!: string };

declare const dictionary: { [index: string]!!: string }

Opt-in (pas d'option de compilateur, optez simplement si nécessaire) :

declare const array: { [index: string]!!: string };

declare const dictionary: { [index: string]??: string }

Je n'ai pas lu sur ce problème ou les avantages et les inconvénients, diverses propositions, etc. suggestion associée : une option pour rendre l'inférence de type de tableau aussi stricte que possible, à moins qu'elle ne soit spécifiquement remplacée.

Par exemple:

const balls = [1, 2 ,3];

Par défaut, balls serait traité comme [number, number, number] . Cela peut être annulé en écrivant :

const balls: number[] = [1, 2 ,3];

De plus, l'accès aux éléments de tuple serait géré de manière cohérente avec des vérifications nulles strictes. Il est surprenant pour moi que dans l'exemple suivant, n soit actuellement déduit comme number même avec les contrôles null stricts activés.

const balls: [number, number, number] = [1, 2 ,3];
const n = balls[100];

Je m'attendrais également à ce que les méthodes de mutation de tableau telles que .push n'existent pas dans la définition du type de tuple, car ces méthodes modifient le type d'exécution pour qu'il soit incohérent avec le type de compilation.

Joli! Eh bien, c'est un timing intéressant. J'avais ouvert l'annonce de sortie, mais je ne l'avais pas encore lu ; n'est venu ici qu'après avoir rencontré une situation où j'avais besoin de faire un casting étrange ( (<(T|undefined)[]> arr).slice(-1)[0] ) pour que TypeScript (2.9) fasse ce que je voulais qu'il fasse.

Je voulais juste ramener les choses à cette suggestion : https://github.com/Microsoft/TypeScript/issues/13778#issuecomment -383072468

Cela résoudrait les problèmes avec les types indexés que j'ai rencontrés. Ce serait génial si c'était la valeur par défaut, mais je comprends que cela casserait beaucoup de choses dans le monde réel.

@mhegazy @RyanCavanaugh Des réflexions sur ma proposition ? https://github.com/Microsoft/TypeScript/issues/13778#issuecomment-406316164

Pour moi, il existe une solution simple. Activez un indicateur qui vérifie cela. Ensuite, au lieu de :

const array = [1, 2, 3];
for (var i =0; i< array.length; i++) {
    array[i]+=1; // array[i] is possibly undefined
}

Tu fais:

const array = [1, 2, 3];
array.forEach((value, i) => array[i] = value + 1);

Ensuite, lorsque vous effectuez des accès d'index aléatoires, vous devez vérifier si le résultat n'est pas défini, mais pas lors de l'itération d'une collection énumérée.

Je pense toujours que cela justifie d'avoir un problème ouvert.

En tant que programmeur novice en TypeScript, j'ai trouvé que la situation autour de l'indexation d'objets en mode strict n'était pas intuitive. Je me serais attendu à ce que le résultat d'une recherche soit T | undefined , de la même manière que Map.get . Quoi qu'il en soit, je viens de rencontrer cela récemment et j'ai ouvert un problème dans une bibliothèque :

screepers/dactyd-screeps#107

Je vais probablement le fermer maintenant, car il semble qu'il n'y ait pas de bonne solution. Je pense que je vais essayer de " m'inscrire " à T | undefined en utilisant une petite fonction utilitaire :

export function lookup<T>(map: {[index: string]: T}, index: string): T|undefined {
  return map[index];
}

Il y a eu quelques suggestions ici pour définir explicitement T | undefined comme type de retour d'opération d'index d'objet, mais cela ne semble pas fonctionner :

const obj: {[key: string]: number | undefined} = {
  "a": 1,
  "b": 2,
};

const test = obj["c"]; // const test: number

Il s'agit de la version 1.31.1 de VSCode

@yawaramin Assurez-vous que strictNullChecks activé dans votre tsconfig.json (qui est également activé par le drapeau strict )

Si votre cas d'utilisation nécessite une indexation arbitraire sur des tableaux de longueur inconnue, je pense qu'il vaut la peine d'ajouter explicitement l'undefined (ne serait-ce que pour "documenter" cette insécurité).

const words = ... // some string[] that could be empty
const x = words[0] as string | undefined
console.log(x.length) // TS error

Les tuples fonctionnent pour de petits tableaux de longueurs connues. Peut-être pourrions-nous avoir quelque chose comme string[5] comme raccourci pour [string, string, string, string, string] ?

Très favorable à cette option. C'est un trou notable dans le système de types, en particulier lorsque le drapeau strictNullChecks est activé. Les objets simples sont utilisés comme mappes tout le temps dans JS, donc TypeScript devrait prendre en charge ce cas d'utilisation.

J'ai rencontré ceci avec la destruction d'un tableau d'un paramètre de fonction :

function foo([first]: string[]) { /* ... */ }

Ici, je m'attendrais à ce que a soit du type string | undefined mais c'est juste string , à moins que je ne le fasse

function foo([first]: (string | undefined)[]) { /* ... */ }

Je ne pense pas que nous ayons une seule boucle for dans notre base de code, donc je serais heureux d'ajouter un indicateur aux options du compilateur de mon tsconfig (pourrait être nommé strictIndexSignatures ) pour basculer ce comportement pour notre projet.

Voici comment j'ai travaillé sur ce problème : https://github.com/danielnixon/total-functions/

Bonne solution de contournement, j'espère vraiment que cela sera bloqué par l'équipe TypeScript.

Lorsqu'un programmeur fait des hypothèses dans du code écrit et que le compilateur ne peut pas en déduire qu'il s'agit d'une sauvegarde, cela devrait entraîner une erreur du compilateur à moins d'être réduit au silence à mon humble avis.

Ce comportement serait également très utile en combinaison avec le nouvel opérateur de chaînage facultatif.

J'ai rencontré un problème avec l'utilisation de | undefined avec la carte aujourd'hui tout en utilisant Object.entries() .

J'ai un type d'index qui est assez bien décrit par {[key: string]: string[]} , avec la mise en garde évidente que toutes les clés de chaîne possibles ne sont pas représentées dans l'index. Cependant, j'ai écrit un bogue que TS n'a pas détecté en essayant de consommer une valeur recherchée dans l'index ; n'a pas géré le cas indéfini.

Je l'ai donc changé en {[key: string]: string[] | undefined} comme recommandé, mais cela entraîne maintenant des problèmes avec mon utilisation de Object.entries() . TypeScript suppose maintenant (raisonnablement, sur la base de la spécification de type) que l'index peut avoir des clés qui ont une valeur non définie spécifiée, et suppose donc que le résultat de l'appel de Object.entries() dessus peut contenir des valeurs non définies.

Cependant, je sais que cela est impossible; la seule fois où je devrais obtenir un résultat undefined est de rechercher une clé qui n'existe pas et qui ne serait pas répertoriée lors de l'utilisation de Object.entries() . Donc, pour rendre TypeScript heureux, je dois soit écrire du code qui n'a aucune raison réelle d'exister, soit écraser les avertissements, ce que je préférerais ne pas faire.

@RyanCavanaugh , je suppose que votre réponse initiale à ceci est toujours la position actuelle de l'équipe TS? À la fois comme une bosse à cela parce que personne ne lit le fil et en vérifiant au cas où quelques années d'expérience supplémentaires avec des primitives de collection JS plus puissantes, et en introduisant plusieurs autres options pour augmenter la rigueur depuis n'ont rien changé.

(Les exemples là-bas ne sont toujours pas convaincants pour moi, mais ce fil a déjà fait tous les arguments, un autre commentaire ne va rien changer)

Si quelqu'un veut modifier son lib.d.ts et corriger toutes les ruptures en aval dans son propre code et montrer à quoi ressemble la différence globale pour montrer que cela a une proposition de valeur, nous sommes ouverts à ces données.

@RyanCavanaugh Voici quelques -

src={savedAdsItem.advertImageList.advertImage[0].mainImageUrl || undefined}
return advert.advertImageList.advertImage.length ? advert.advertImageList.advertImage[0].mainImageUrl : ''
birthYear: profileData.birthYear !== null ? profileData.birthYear : allowedYears[0].value,
upsellingsList.upsellingProducts[0].upsellingProducts[0].selected = true
const latitude = parseFloat(coordinates.split(',')[0])
const advert = Object.values(actionToConfirm.selectedItems)[0]
await dispatch(deactivateMyAd(advert))

Dans ce cas, ce serait ennuyeux car ArticleIDs extends articleNames[] inclut undefined dans les valeurs résultantes, alors qu'il ne devrait autoriser que des sous-ensembles complètement définis. Facilement réparable en utilisant ReadonlyArray<articleNames> au lieu de articleNames[] .

export enum articleNames {
    WEB_AGB = 'web_agb',
    TERMS_OF_USE = 'web_terms-of-use',
}
export const getMultipleArticles = async <ArticleIDs extends articleNames[], ArticleMap = { [key in ArticleIDs[number]]: CmsArticle }>(ids: ArticleIDs): Promise<ArticleMap> => {...}

Dans l'ensemble, j'aimerais vraiment avoir cette sécurité de type supplémentaire pour éviter d'éventuels plantages d'exécution.

Je suis entré un peu plus dans le raisonnement à ce commentaire #11238 (commentaire)

Pensez aux deux types de clés dans le monde : celles dont vous savez qu'elles ont une propriété correspondante dans un objet (coffre-fort), celles dont vous ne savez pas qu'elles ont une propriété correspondante dans un objet (dangereux).

Vous obtenez le premier type de clé, une clé "sûre", en écrivant le code correct comme

for (let i = 0; i < arr.length; i++) {
  // arr[i] is T, not T | undefined

ou

for (const k of Object.keys(obj)) {
  // obj[k] is T, not T | undefined

Vous obtenez le deuxième type à partir de la clé, le type "dangereux", à partir d'éléments tels que les entrées utilisateur, les fichiers JSON aléatoires du disque, ou une liste de clés pouvant être présentes mais non.

Donc, si vous avez une clé dangereuse et que vous l'indexez, ce serait bien d'avoir | undefined ici. Mais la proposition n'est pas "Traiter les clés _dangereuses_ comme dangereuses", c'est "Traiter _toutes_ les clés, même celles qui sont sûres, comme dangereuses". Et une fois que vous commencez à considérer les clés sûres comme dangereuses, la vie est vraiment nulle. Vous écrivez du code comme

for (let i = 0; i < arr.length; i++) {
  console.log(arr[i].name);

et TypeScript se plaint que arr[i] pourrait être undefined même si _hey regarde, je viens de @#%#ing tester pour ça_. Maintenant, vous prenez l'habitude d'écrire du code comme celui-ci, et cela semble stupide :

for (let i = 0; i < arr.length; i++) {
  // TypeScript makes me use ! with my arrays, sad.
  console.log(arr[i]!.name);

Ou peut-être écrivez-vous du code comme celui-ci :

function doSomething(myObj: T, yourObj: T) {
  for (const k of Object.keys(myObj)) {
    console.log(yourObj[k].name);
  }
}

et TypeScript dit "Hé, cette expression d'index pourrait être | undefined , donc vous la corrigez consciencieusement car vous avez déjà vu cette erreur 800 fois :

function doSomething(myObj: T, yourObj: T) {
  for (const k of Object.keys(myObj)) {
    console.log(yourObj[k]!.name); // Shut up TypeScript I know what I'm doing
  }
}

Mais vous n'avez pas corrigé le bug . Vous vouliez écrire Object.keys(yourObj) , ou peut-être myObj[k] . C'est le pire type d'erreur de compilation, car cela ne vous aide dans aucun scénario - il s'agit uniquement d'appliquer le même rituel à chaque type d'expression, sans se soucier du fait qu'il soit ou non plus dangereux que toute autre expression de la même forme.

Je pense à l'ancien « Êtes-vous sûr de vouloir supprimer ce fichier ? » dialogue. Si cette boîte de dialogue apparaissait à chaque fois que vous essayiez de supprimer un fichier, vous apprendriez très rapidement à frapper del y alors que vous aviez l'habitude d'appuyer sur del , et vos chances de ne pas supprimer quelque chose d'important sont réinitialisées -dialogue de base. Si au lieu de cela, la boîte de dialogue n'apparaissait que lorsque vous supprimiez des fichiers alors qu'ils n'allaient pas dans la corbeille de recyclage, vous disposez désormais d'une sécurité significative. Mais nous n'avons aucune idée (ni _pouvions_ nous) si vos clés d'objet sont sûres ou non, donc l'affichage du message "Êtes-vous sûr de vouloir indexer cet objet ?" dialogue chaque fois que vous le faites, il est peu probable que vous trouviez des bogues à un meilleur taux que de ne pas tout afficher.

Je suis d'accord avec l'analogie de la boîte de dialogue de suppression de fichier, mais je pense que cette analogie peut également être étendue pour forcer l'utilisateur à vérifier quelque chose qui est peut-être indéfini ou nul, donc cette explication n'a pas vraiment de sens, car si cette explication est vraie, l'option strictNullChecks va induire le même comportement, par exemple obtenir un élément du DOM en utilisant document.getElementById .

Mais ce n'est pas le cas, beaucoup d'utilisateurs de TypeScript veulent que le compilateur lève un indicateur sur un tel code afin que ces cas extrêmes puissent être traités de manière appropriée au lieu de lancer l'erreur Cannot access property X of undefined qui est très très difficile à tracer.

En fin de compte, j'espère que ce type de fonctionnalités pourra être implémenté en tant qu'options de compilateur TypeScript supplémentaires, car c'est la raison pour laquelle les utilisateurs veulent utiliser TypeScript, ils veulent être avertis du code dangereux.

Parler d'accéder à tort à un tableau ou à des objets est peu probable, avez-vous des données pour sauvegarder cette affirmation ? Ou est-ce simplement basé sur une intuition arbitraire ?

for (let i = 0; i < arr.length; i++) {
  console.log(arr[i].name);

L'analyse de type basée sur le flux de contrôle de TypeScript pourrait être améliorée pour reconnaître que ce cas est sûr et ne nécessite pas le ! . Si un humain peut en déduire qu'il est sûr, un compilateur le peut aussi.
Je suis conscient que cela pourrait être non trivial à implémenter dans le compilateur.

L'analyse de type basée sur le flux de contrôle de TypeScript pourrait être améliorée pour reconnaître que ce cas est sûr

Il ne peut vraiment pas.

declare function someFunc(arr: number[], i: number): void;
let arr = [1, 2, 3, 4];
for (let i = 0; i < arr.length; i++) {
  someFunc(arr, arr[i]);
}

Cette fonction passe-t-elle un undefined à someFunc lors du deuxième passage dans la boucle, ou non ? Il y a beaucoup de choses que je pourrais écrire dans someFunc qui résulteraient en un undefined apparaissant plus tard.

Et ça?

declare function someFunc(arr: number[], i: number): void;
let arr = [1, 2, 3, 4];
let alias = arr;
for (let i = 0; i < arr.length; i++) {
  someFunc(alias, arr[i]);
}

@fabb permettez-moi de donner un autre exemple :

```
$ nœud

const arr = []
indéfini
arr[7] = 7
7
arr
[ <7 éléments vides>, 7 ]
for (let i = 0; i < arr.length; i++) {
... console.log(arr[i])
... }
indéfini
indéfini
indéfini
indéfini
indéfini
indéfini
indéfini
7
non défini```

@RyanCavanaugh puisque vous êtes ici, que diriez-vous d'inférer item :: T pour arr :: T[] dans for (const item of arr) ... , et sinon d'inférer arr[i] :: T | undefined lors de l'utilisation de --strict-index ? Et le cas qui m'importe, obj[key] :: V | undefined mais Object.values(obj) :: V[] pour obj :: { [key: string]: V } ?

@yawaramin Si vous utilisez des tableaux clairsemés, Typescript ne fait déjà pas ce qu'il faut. Un indicateur --strict-index résoudrait cela. Cela compile :

const arr = []
arr[7] = 7

for (let i = 0; i < arr.length; i++) {
    console.log(Math.sqrt(arr[i]));
}

@RyanCavanaugh Il y a un exemple très courant que je peux vous montrer où l'utilisateur est enclin à accéder incorrectement au tableau.

const getBlock = (unitNumber: string): string => unitNumber.split('-')[0]

Le code ci-dessus ne doit pas être transmis au compilateur en vérifiant gracieusement sous strictNullChecks , car certaines utilisations de getBlock renverront undefined, par exemple getBlock('hello') , dans de tels cas, je veux sérieusement le compiler l'indicateur de relance afin que je puisse gérer les cas non définis avec élégance sans exploser mon application.

Et cela s'applique également à de nombreux idiomes courants tels que l'accès au dernier élément d'un tableau avec arr.slice(-1)[0] , l'accès au premier élément arr[0] etc.

En fin de compte, je veux que TypeScript m'ennuie pour de telles erreurs plutôt que d'avoir à faire face à des applications éclatées.

Cette fonction passe-t-elle un indéfini à someFunc lors du deuxième passage dans la boucle, ou non ? Il y a beaucoup de choses que je pourrais écrire dans someFunc qui entraîneraient une apparition plus tard indéfinie.

@RyanCavanaugh Oui, JavaScript ne se prête pas à l'immuabilité. Dans ce cas, soit un ! devrait être nécessaire, soit un tableau ReadonlyArray : someFunc(arr: ReadonlyArray<number>, i: number) .

@yawaramin Pour les tableaux clairsemés, le type d'élément doit probablement inclure undefined moins que TypeScript puisse en déduire qu'il est utilisé comme un tuple. Dans le code auquel @danielnixon est lié (https://github.com/microsoft/TypeScript/issues/13778#issuecomment-536248028), les tuples sont également traités de manière spéciale et n'incluent pas undefined dans le type d'élément renvoyé puisque le compilateur garantit que seuls les indices définis sont accessibles.

c'est une décision consciente. ce serait très ennuyeux que ce code soit une erreur :

var a = [];
for (var i =0; i< a.length; i++) {
    a[i]+=1; // a[i] is possibly undefined
}

Hé, je reconnais cette syntaxe ; j'écris des boucles comme celle-ci peut-être une fois par an !

J'ai trouvé que dans la plupart des cas où je fais un index dans un tableau, je veux en fait vérifier undefined par la suite.

Les craintes de rendre ce cas particulier plus difficile à traiter semblent exagérées. Si vous souhaitez simplement parcourir les éléments, vous pouvez utiliser for .. of . Si vous avez besoin de l'index d'élément pour une raison quelconque, utilisez forEach ou itérez sur entries . En général, il est extrêmement rare que vous ayez vraiment besoin d'une boucle for basée sur un index.

S'il y a de meilleures raisons pour lesquelles on voudrait le statu quo, j'aimerais les voir, mais peu importe : il s'agit d'une incohérence dans le système et avoir un indicateur pour le corriger serait très apprécié par beaucoup, semble-t-il.

Salut tout le monde, j'ai l'impression qu'une grande partie de la discussion à avoir ici a été
avais. Les propriétaires de paquets ont été assez clairs sur leur raisonnement et
ont déjà examiné la plupart de ces arguments. S'ils prévoient de s'attaquer
cela, je suis sûr qu'ils le feront savoir. A part ça, je ne pense pas que ça
le fil est vraiment productif.

Le vendredi 25 octobre 2019 à 11 h 59, brunnerh [email protected] a écrit :

c'est une décision consciente. il serait très ennuyeux que ce code
être une erreur :

var a = [];for (var i =0; i< a.length; i++) {
a[i]+=1 ; // a[i] est peut-être indéfini
}

Hé, je reconnais cette syntaxe ; j'écris des boucles comme celle-ci peut-être une fois par an !

J'ai trouvé que dans la plupart des cas où je fais un index dans un tableau, je
voulez en fait vérifier undefined par la suite.

Les craintes de rendre ce cas particulier plus difficile à traiter semblent exagérées.
Si vous souhaitez simplement parcourir les éléments que vous pouvez utiliser pour .. of. Si
vous avez besoin de l'index de l'élément pour une raison quelconque, utilisez forEach ou itérez sur
entrées. En général, il est extrêmement rare que vous ayez vraiment besoin d'un
boucle for basée sur un index.

S'il y a de meilleures raisons de vouloir le statu quo, je
aime les voir, mais peu importe : il s'agit d'une incohérence dans le système
et avoir un drapeau pour le réparer serait très apprécié par beaucoup, semble-t-il.

-
Vous recevez ceci parce que vous avez commenté.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/microsoft/TypeScript/issues/13778?email_source=notifications&email_token=ACAJU3DQ7U6Y3MUUM26J4JDQQM62XA5CNFSM4C6KEKAKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMV5WWYZLOJKIE ,
ou se désinscrire
https://github.com/notifications/unsubscribe-auth/ACAJU3EWVM3CUFG25UF5PGDQQM62XANCNFSM4C6KEKAA
.

J'ai l'impression que, même s'il n'y a peut-être pas de mouvement de la part des propriétaires de packages, les commentaires continus de la communauté sont toujours précieux, car ils montrent qu'il existe toujours une demande pour de meilleurs outils autour de ce problème.

@brunnerh Je suis d'accord avec toi, de nos jours je n'ai même pas besoin d'utiliser des boucles for sauf si c'est pour le réglage des performances, ce qui arrive 0% du temps dans la base de code de mon entreprise, car la plupart du temps change de carte /filter/reduce to for loop améliore rarement les performances, le vrai coupable est toujours une logique inefficace, un problème de réseau et des connexions à la base de données.

Je suis surpris que personne n'ait encore parlé de as const .

const test = [1, 2, 3] as const;

(test[100]).toFixed(5);
// Tuple type 'readonly [1, 2, 3]' of length '3' has no element at index '100'.

Plus généralement, et pas exactement lié au message initial de ce numéro, j'utilise les modèles de programmation défensifs suivants depuis des mois, et cela a bien fonctionné (pour moi)

const xs: Array<number | undefined> = [1,2,3];

// for objects but kind of related as well
Record<string, User | undefined>

interface Something {
  [key: string]: User | undefined
}

Même s'il n'y a pas de notation courte pour cela (comme ? ), je pense que ça va. Dire vous-même au compilateur que vous n'êtes peut-être pas sûr que la valeur est définie est très bien à mon humble avis.

@martpie as const est génial si vous pouvez l'utiliser, c'est-à-dire.

Il y a au moins deux raisons pour lesquelles je préfère ne pas utiliser Array<T | undefined> :

  1. C'est une chose de plus que vous devez vous rappeler de faire chaque fois que vous utilisez un tableau. De plus, vous ne pouvez plus utiliser la saisie implicite, ce qui est agréable à avoir.
  2. Il modifie les signatures de forEach , map et filter , qui ne passent pas undefined comme argument d'élément (à moins que cet index ne soit explicitement défini de cette façon). Selon combien vous utilisez ces fonctions qui pourraient être ennuyeuses à gérer.

Je tiens également à souligner que cela provoque maintenant beaucoup de faux positifs dans eslint/typescript :

const a: string[] = [];
const foo = a[1000];
if (foo) { // eslint says this is an unnecessary conditional
  console.log(foo.length);
}

Eslint déduit (correctement) du type qu'il s'agit d'une vérification inutile, car foo ne peut jamais être nul. Alors maintenant, non seulement je dois me souvenir consciemment de faire une vérification nulle sur les choses que je récupère d'un tableau, mais je dois aussi ajouter une ligne de désactivation eslint! Et accéder à des choses en dehors d'une boucle for comme celle-ci est pratiquement la seule façon d'accéder aux tableaux, car (comme probablement la plupart des développeurs TS de nos jours), nous utilisons les fonctions forEach/map/filter/etc lors de la boucle sur des tableaux.

Je pense vraiment que nous devrions juste avoir un nouveau drapeau de compilateur qui est défini sur vrai par défaut si strict est vrai, et si les gens ne l'aiment pas, ils peuvent se retirer. Nous avons de toute façon ajouté de nouvelles vérifications strictes du compilateur dans les versions récentes.

C'est très probablement la source des bogues d'exécution _only_ que j'ai vus dans la mémoire récente et qui étaient un échec du type système.

Compte tenu de l'ergonomie de l'opérateur de chaînage en option désormais disponible, il est peut-être temps de revoir la décision de _pas_ rendre ce comportement disponible via un indicateur ?

Si je reçois un tableau de Thing du serveur et que je dois afficher le dernier Thing du tableau, quelque chose que je rencontre couramment est que le tableau est en fait vide et accède à une propriété sur ce Thing planter l'application.

Exemple:

// `things` is `Thing[]`, but is empty, i.e., `[]`
const { things } = data; 

// We are accessing `things[-1]`, which is obviously `undefined`, 
// but TypeScript thinks `latestThing` is a `Thing`
const latestThing = things[things.length - 1];

// TypeError: Cannot read property 'foo' of undefined
return latestThing.foo; 

C'est peut-être un problème avec la conception de notre API, mais il semble vraiment que TS devrait comprendre que lorsque vous accédez à quelque chose dans le tableau, il peut ne pas être là.

Edit : Je veux juste préciser que je voulais dire "notre API" comme dans "l'API créée par l'équipe sur laquelle je travaille"

oui, tout le monde est à peu près d'accord pour dire que c'est une très mauvaise décision de conception, contemporaine d'une époque lointaine où TS était complètement dangereux.

Comme solution de contournement, j'utilise un petit hack sale. Je viens d'ajouter dans package.json le script de post-installation simple :

{
...
  "scripts": {
    "postinstall": "sed -i 's/\\[n: number\\]: T;/[n: number]: T | undefined;/g' node_modules/typescript/lib/lib.es5.d.ts",
    ...
  },
...
}

Bien sûr, cela ne fonctionnera pas sur Windows, mais c'est mieux que rien.

Tous les autres problèmes qui s'exécutent à côté de celui-ci semblent être redirigés ici, donc je suppose que c'est le meilleur endroit pour demander : une syntaxe abrégée pour les signatures d'index facultatives est-elle hors de la table ? Comme le souligne @martpie , il faut écrire interface Foo { [k: string]: Bar | undefined; } qui est moins ergonomique que interface Foo { [k: string]?: Bar; } .

Pouvons-nous obtenir l'assistance de l'opérateur ?: ? Si non, pourquoi pas ? C'est-à-dire que l'avantage ergonomique était suffisant pour ajouter la fonctionnalité de définition de propriété unique - n'est-ce pas "assez utile" pour les signatures d'index ?

type Foo = { [_ in string]?: Bar } fonctionne également. Pas aussi joli, mais assez laconique. Vous pouvez également créer votre propre type Dict si vous le souhaitez. Je ne contesterais pas une extension ?: , cependant

Cela commence à ressembler à l'un de ces discours "Javascript: The Bad Parts":

ts type Foo1 = { [_ in string]?: Bar } // Yup type Foo2 = { [_: string]?: Bar } // Nope interface Foo3 { k?: Bar } // Yup interface Foo4 { [_: string]?: Bar } // Nope

Utiliser T | undefined pour la signature de type n'est vraiment pas utile. Nous avons besoin d'un moyen de faire en sorte que l'opérateur d'index [n] ait un type T|undefined , mais par exemple, utiliser map sur un tableau ne devrait T|undefined valeurs, car dans cette situation, nous devrions savoir qu'elles existent.

@radix Pour les fonctions réelles sur Array, je ne pense pas que ce sera un problème car elles ont toutes leurs propres définitions de type qui génèrent le bon type : par exemple map : https://github.com/microsoft /TypeScript/blob/master/lib/lib.es5.d.ts#L1331

La seule utilisation courante du code que la construction | undefined pose une régression réelle de l'expérience est dans les boucles for ... of . Malheureusement (du point de vue de cette question), ce sont des constructions assez courantes.

@riggs Je pense que vous parlez de faire interface Array<T> { } avoir [index: number]: T | undefined , mais @radix parle probablement de ce qui semble être la recommandation actuelle, qui est d'utiliser Array<T | undefined> dans votre propre code.

Ce dernier est mauvais pour plusieurs raisons, notamment que vous ne contrôlez pas les types d'autres packages, mais le premier a également quelques problèmes, à savoir que vous pouvez affecter undefined au tableau, et qu'il donne undefined même dans des cas connus pour être sûrs. 🤷‍♂️

Ahh, oui, mon incompréhension. En effet, je ne faisais référence qu'à l'utilisation de la définition [index: number]: T | undefined . Je suis tout à fait d'accord pour dire que définir un type comme Array<T | undefined> est une terrible solution de contournement qui cause plus de problèmes qu'elle n'en résout.

Existe-t-il un moyen gracieux de remplacer lib.es5.d.ts ou un script de post-installation est-il le meilleur moyen de procéder ?

@nicu-chiciuc https://www.npmjs.com/package/patch-package S'intègre totalement dans la boîte à outils d'un hérétique aux côtés de https://www.npmjs.com/package/yalc ✌️

Existe-t-il un moyen gracieux de remplacer lib.es5.d.ts ou un script de post-installation est-il le meilleur moyen de procéder ?

Dans notre projet, nous avons un fichier global.d.ts que nous utilisons pour 1) ajouter des définitions de types pour les API intégrées qui ne sont pas encore dans les définitions de type par défaut de typescript (par exemple, les API liées à WebRTC qui changent constamment et sont incohérentes entre les navigateurs ) et 2) en remplaçant certaines des définitions de type par défaut de typescript (par exemple, en remplaçant le type de Object.entries afin qu'il renvoie des tableaux contenant unknown au lieu de any ).

Je ne sais pas si cette approche pourrait être utilisée pour remplacer les types de tableau de typescript pour résoudre ce problème, mais cela pourrait valoir la peine d'essayer.

Ce qui précède fonctionne lorsque la fusion des déclarations donne le résultat souhaité, mais pour simplifier, elles se croisent avec des interfaces, mais ici, l'option la moins restrictive est ce que vous voulez.

Vous pouvez plutôt essayer de copier-coller tous les fichiers lib.*.d.ts vous utilisez dans votre projet, inclus dans tsconfig.json de files ou include , puis les modifier à ce que vous voulez. Puisqu'ils incluent /// <reference no-default-lib="true"/> , vous ne devriez pas avoir besoin d'autre magie, mais je ne sais pas si vous devez supprimer les /// <reference lib="..."/> qu'ils ont dans leurs dépendances. C'est mauvais pour toutes les raisons évidentes de maintenabilité, bien sûr.

@butchler Nous filter avec des gardes de type) avec succès.

J'ai un cas étrange avec le remplacement de l'opérateur d'indexation :

function test() {
  const arr: string[] = [];

  const [first] = arr;
  const zero = arr[0];

  const str1: string = first;
  const str2: string = zero;
}

Screenshot 2020-02-05 at 10 39 20 AM

La deuxième affectation se trompe (comme il se doit), mais pas la première.
Ce qui est encore plus étrange, c'est que le survol de first alors qu'il est déstructuré montre qu'il a un type de string | undefined que le survol pendant qu'il est assigné montre qu'il a un type de string .
Screenshot 2020-02-05 at 10 40 25 AM
Screenshot 2020-02-05 at 10 40 32 AM

Existe-t-il une définition de type différente pour la déstructuration des tableaux ?

À la recherche d'une solution à ce problème, car les erreurs liées aux index manquants ont été une source fréquente de bogues dans mon code.

Spécifier un type comme { [index: string]: string | undefined } n'est pas une solution, car cela gâche la saisie pour les itérateurs comme Object.values(x).forEach(...) qui n'incluront jamais les valeurs undefined .

J'aimerais voir TypeScript générer des erreurs lorsque je ne vérifie pas undefined après avoir fait someObject[someKey] , mais pas en faisant Object.values(someObject).forEach(...) .

@danielnixon Ce n'est pas une solution, c'est une solution de contournement. Rien ne vous empêche, vous ou un autre développeur, d'utiliser par erreur (si vous pouvez même l'appeler ainsi) les outils intégrés du langage pour les mêmes objectifs. Je veux dire, j'utilise fp-ts pour ce genre de choses, mais ce problème est toujours valable et doit être corrigé.

Bien que je puisse suivre les arguments contraires de

for (let i = 0; i < arr.length; i++) {
  // TypeScript makes me use ! with my arrays, sad.
  console.log(arr[i]!.name);

Je laisserais quelques brèves réflexions à ce sujet. À la base, TypeScript vise à rendre le développement, bien... plus sûr. Pour se souvenir du premier objectif de TypeScript :

1. Identifiez de manière statique les constructions susceptibles d'être des erreurs.

Si nous regardons le compilateur, nous avons déjà un peu de magie derrière l'inférence de type. Dans le cas particulier, la véritable excuse est : "nous ne pouvons pas déduire le bon type sur cette construction" et c'est tout à fait correct. Au fil du temps, nous avons de moins en moins de cas, où compile n'est pas en mesure de déduire les types. En d'autres termes, pour moi, c'est juste une question de temps (et de passer du temps là-dessus) pour obtenir une inférence de type plus sophistiquée.

D'un point de vue technique, des constructions telles que :

const x = ['a', 'b', 'c']
console.log(x[3]) // type: string, reality: undefined

brise le premier objectif de TypeScript. Mais cela ne se produit pas si TypeScript connaît les types les plus précis :

const x = ['a', 'b', 'c'] as const
console.log(x[3]) // compile error: Tuple type 'readonly ["a", "b", "c"]' of length '3' has no element at index '3'.ts(2493)

D'un point de vue pratique, c'est un compromis yet . Ce problème et plusieurs problèmes clos montrent qu'il y a une forte demande de la communauté pour ce changement : ATM 238 upvoters contre 2 downvoters. Bien sûr, il est dommage que la boucle for ci-dessus n'infère pas le "bon" type, mais bon, je suis à peu près sûr que la plupart des votants peuvent vivre avec ! et le nouveau ? comme signe d'attention et forcez-le dans des cas sûrs. Mais de l'autre côté, obtenez les bons types sur l'accès "dangereux".

Parce que je suis d'accord que c'est un changement très sensible pour les grosses bases de code, je vote pour une propriété de configuration, comme cela a été proposé ici . Au moins pour obtenir des commentaires de la communauté. Si ce n'est pas possible, alors, TS 4.0 devrait l'obtenir.

Ce n'est pas une solution proposée, mais juste une expérience : j'ai essayé de modifier lib.es5.d.ts dans mon node_modules sur un projet existant qui utilise TypeScript juste pour voir à quoi cela ressemblerait si nous avions un compilateur option pour cela. J'ai modifié Array et ReadonlyArray :

interface ReadonlyArray<T> {
  ...
  [n: number]: T | undefined; // was just T
}

interface Array<T> {
  ...
  [n: number]: T | undefined; // was just T
}

Comme il s'agit d'un très grand projet, cela a causé plusieurs centaines d'erreurs de type. Je n'en ai parcouru que quelques-uns pour avoir une idée du type de problèmes que je rencontrerais et de la difficulté à les contourner s'il y avait une option de compilateur pour cela. Voici quelques-uns des problèmes que j'ai rencontrés :

  1. Cela a causé des erreurs de type non seulement dans notre base de code, mais dans l'une de nos dépendances : io-ts. Étant donné que io-ts vous permet de créer des types que vous utilisez dans votre propre code, je ne pense pas qu'il soit possible d'appliquer cette option uniquement à votre propre base de code et de ne pas l'appliquer également aux types d'io-ts. Cela signifie que io-ts et probablement d'autres bibliothèques devraient encore être mis à jour pour fonctionner avec cette option, même si elle n'était introduite qu'en tant qu'option de compilateur. Au début, je pensais que faire de cette option une option de compilateur rendrait cela moins controversé, mais si les personnes qui choisissent d'utiliser l'option commencent à se plaindre d'incompatibilités auprès de différents auteurs de bibliothèques, cela peut être encore plus controversé que d'être simplement un TS 4.0 changement de rupture.

  2. Parfois, je devais ajouter des gardes de type supplémentaires pour éliminer la possibilité d'undefined. Ce n'est pas nécessairement une mauvaise chose, et c'est en quelque sorte l'intérêt de cette proposition, mais je ne la mentionne que pour être complet.

  3. J'ai eu une erreur de type dans une boucle for (let i = 0; i < array.length; i++) qui bouclait sur un Array<T> arbitraire. Je ne pouvais pas simplement ajouter un type guard pour vérifier undefined , car T lui-même pourrait inclure undefined . Les seules solutions auxquelles je pouvais penser étaient A) d'utiliser une assertion de type ou @ts-ignore pour faire taire l'erreur de type ou B) d'utiliser une boucle for-of à la place. Personnellement, je ne trouve pas cela trop grave (il y a probablement peu de cas où l'utilisation d'une boucle for-of pour itérer sur un tableau n'est pas mieux de toute façon), mais cela peut être controversé.

  4. Il existe de nombreux cas où le code existant faisait déjà une affirmation sur la valeur de .length , puis accédait à un élément du tableau. Ceux-ci provoquaient maintenant des erreurs de type malgré le contrôle .length , j'ai donc dû soit modifier le code pour ne pas dépendre d'un contrôle .length , soit simplement ajouter un contrôle !== undefined redondant. Ce serait vraiment bien si TypeScript pouvait permettre d'une manière ou d'une autre d'utiliser un contrôle .length pour éviter d'avoir besoin du contrôle !== undefined . Je suppose que la mise en œuvre réelle ne serait pas triviale, cependant.

  5. Certains codes utilisaient A[number] pour obtenir le type des éléments d'un type de tableau générique. Cependant, cela renvoyait maintenant T | undefined au lieu de seulement T , provoquant des erreurs de type ailleurs. J'ai fait une aide pour contourner ceci:

    type ArrayValueType<A extends { [n: number]: unknown }> = (
      A extends Array<infer T> ? T :
      A extends ReadonlyArray<infer T> ? T :
      A[number] // Fall back to old way of getting array element type
    );
    

    mais même avec cette aide pour faciliter la transition, c'est toujours un grand changement de rupture. Peut-être que TypeScript pourrait avoir une sorte de cas spécial pour A[number] pour éviter ce problème, mais ce serait bien d'éviter des cas spéciaux étranges comme celui-ci si possible.

Je n'ai parcouru qu'une petite poignée des centaines d'erreurs de type, il s'agit donc probablement d'une liste très incomplète.

Pour toute autre personne intéressée à résoudre ce problème, il peut être utile d'essayer de faire la même chose avec certains projets existants et de partager les autres problèmes que vous rencontrez. Espérons que les exemples spécifiques peuvent fournir des conseils à quiconque essaie réellement de mettre en œuvre cela.

@butchler J'ai également rencontré certains de ces problèmes, j'ai commencé à afficher something[i] comme something(i) , c'est-à-dire que je ne peux pas simplement utiliser quelque chose comme if (Meteor.user() && Meteor.user()._id) {...} , donc je ne ' t s'attendre à avoir cette approche pour l'indexation des tableaux ; Je dois d'abord extraire la valeur que je veux inspecter du tableau. Ce que j'essaie de dire, c'est que compter sur TS pour comprendre que vérifier la propriété de longueur et faire une affirmation basée sur cela pourrait mettre trop de pression sur le système de type. Une chose qui m'a fait retarder la transition (outre le fait que certaines autres bibliothèques ont également des erreurs, comme vous l'avez dit) est que la déstructuration du tableau ne fonctionne pas encore correctement et la valeur déstructurée ne sera pas T | undefined mais T (voir mon autre commentaire).
En dehors de cela, je pense que remplacer lib.es5.d.ts est une bonne approche. Même si les choses changent à l'avenir, annuler la modification n'ajoutera pas d'erreurs supplémentaires, mais garantira que certains cas limites sont déjà couverts.
Nous avons déjà commencé à utiliser patch-package pour modifier certaines définitions de type dans react et cette approche semble fonctionner correctement.

J'ai également rencontré certains de ces problèmes, j'ai commencé à voir quelque chose[i] comme quelque chose(i), c'est-à-dire que je ne peux pas simplement utiliser quelque chose comme if (Meteor.user() && Meteor.user()._id) { ...}, donc je ne m'attends pas à avoir cette approche pour l'indexation des tableaux ; Je dois d'abord extraire la valeur que je veux inspecter du tableau.

Oui, j'ai aussi fini par utiliser cette approche lorsque j'essayais mon expérience. Par exemple, le code qui était précédemment écrit sous la forme :

if (array[i]) {
  array[i].doSomething(); // causes a type error with our modified Array types
}

devait être changé en quelque chose comme :

const arrayValue = array[i]
if (arrayValue) {
  arrayValue.doSomething();
}

Ce que j'essaie de dire, c'est que compter sur TS pour comprendre que vérifier la propriété de longueur et faire une affirmation basée sur cela pourrait mettre trop de pression sur le système de type.

Probablement vrai. Il pourrait en fait être plus facile d'écrire un codemod pour réécrire automatiquement du code qui dépend d'assertions sur .length pour utiliser une autre approche, que de rendre TypeScript suffisamment intelligent pour en déduire plus sur les types basés sur des assertions sur le .length 😛

Même si les choses changent à l'avenir, annuler la modification n'ajoutera pas d'erreurs supplémentaires, mais garantira que certains cas limites sont déjà couverts.

C'est un bon point. Bien que l'inclusion de undefined dans les types d'index soit un changement majeur, aller dans l'autre sens n'est pas un changement radical. Le code capable de gérer l'obtention de T | undefined devrait également être capable de gérer T sans aucun changement. Cela signifie, par exemple, que les bibliothèques pourraient être mises à jour pour gérer le cas T | undefined s'il y avait un indicateur de compilateur et être toujours utilisées par des projets qui n'ont pas cet indicateur de compilateur activé.

En ignorant les tableaux et en me concentrant sur Record<string, T> pendant un moment, ma liste de souhaits personnelle est que l'écriture n'autorise que T mais la lecture peut être T|undefined .

declare const obj : Record<string, T>;
declare const t : T;
obj["k"] = t; //ok
obj["k"] = undefined; //error, undefined not assignable to T

//T|undefined inferred,
//since we don't know if "k2" is an "ownProperty" of obj
const v = obj["k2"];

La seule façon pour ce qui précède d'être ergonomique serait une sorte de typage dépendant et des gardes de type dépendant. Comme nous n'en avons pas, l'ajout de ce comportement causerait toutes sortes de problèmes.

//Shouldn't just be string[]
//Should also be something like (keyof valueof obj)[],
//A dependent type
const keys = Object.keys(obj);

Pour en revenir aux tableaux, le problème est que la signature d'index pour les tableaux n'a pas la même intention que Record<number, T> .

Ainsi, vous auriez besoin de gardes de type dépendant entièrement différents comme,

for (let i=0; i<arr.length; ++i) {
  //i is not just number
  //i should also be something like keyof valueof arr 
}

Ainsi, la signature d'index pour les tableaux n'est pas vraiment Record<number, T> . Cela ressemble plus à Record<(int & (0 <= i < this["length"]), T> (une plage et un type d'entier adossé à un nombre)


Ainsi, alors que le message original ne parle que de tableaux, le titre semble suggérer des signatures d'index de "juste" string ou "juste" number . Et ce sont deux discussions complètement différentes.

TL; DR Le message et le titre d'origine parlent de différentes choses, la mise en œuvre de l'une ou l'autre semble fortement dépendante (ha) du typage dépendant.

Étant donné que des fonctions comme forEach , map , filter , etc existent (et sont à mon humble avis beaucoup plus préférables), je ne m'attendrais pas à ce que l'équipe TS complique considérablement leur moteur d'inférence pour prend en charge le bouclage sur un tableau avec une boucle for régulière. J'ai le sentiment qu'il y a tout simplement trop de complexité inattendue à essayer d'accomplir cela. Par exemple, puisque i n'est pas une constante, que se passe-t-il si quelqu'un modifie la valeur de i à l'intérieur de la boucle ? Bien sûr, c'est un cas limite, mais c'est quelque chose qu'ils doivent gérer de manière (espérons-le) intuitive.

La correction des signatures d'index, cependant, devrait être relativement simple (ish), comme les commentaires ci-dessus l'ont montré.

4. Il existe de nombreux cas où le code existant faisait déjà une affirmation sur la valeur de .length , puis accédait à un élément du tableau. Ceux-ci provoquaient maintenant des erreurs de type malgré le contrôle .length , j'ai donc dû soit modifier le code pour ne pas dépendre d'un contrôle .length , soit simplement ajouter un contrôle !== undefined redondant. Ce serait vraiment bien si TypeScript pouvait permettre d'une manière ou d'une autre d'utiliser un contrôle .length pour éviter d'avoir besoin du contrôle !== undefined . Je suppose que la mise en œuvre réelle ne serait pas triviale, cependant.

@boucher
Par curiosité, combien d'entre eux accédaient avec une variable changeante, par rapport à un accès avec un numéro fixe ? Parce que, dans ce dernier cas, vous utilisez probablement le tableau comme un tuple, pour lequel TS garde une trace de la longueur du tableau. Si vous utilisez fréquemment des tableaux en tant que tuples, je serais curieux de savoir ce que les retaper en tant que tuples réels fait au nombre d'erreurs.

Si vous utilisez fréquemment des tableaux en tant que tuples, je serais curieux de savoir ce que les retaper en tant que tuples réels fait au nombre d'erreurs.

J'ai rencontré un cas où j'ai corrigé une erreur de type en ajoutant simplement un as const à un littéral de tableau pour transformer le type de la variable en un tuple. Je n'ai absolument aucune idée à quel point cela est courant dans notre base de code, car je n'ai examiné qu'environ 10 erreurs sur plusieurs centaines.

J'ai également rencontré un problème avec cela aujourd'hui.
Nous accédons à partir d'un tableau via un index calculé. Cet indice pourrait être hors de portée.
Nous avons donc quelque chose comme ceci dans notre code :

const noNext = !items[currentIndex + 1];

Cela signifie que noNext est défini comme false . Ce qui est faux. Cela peut être vrai.
Je ne veux pas non plus définir items comme Array<Item | undefined> car cela donne une mauvaise attente.
Si un index est là, il ne devrait jamais être undefined . Mais si vous utilisez le mauvais index, il _is_ undefined .
Bien sûr, ce qui précède pourrait probablement être résolu en utilisant un contrôle .length place ou en définissant noNext explicitement comme boolean .
Mais au final c'est quelque chose qui me dérange depuis que j'ai commencé à utiliser TypeScript et je n'ai jamais compris pourquoi | undefined n'est pas inclus par défaut.

Étant donné que je m'attends à ce que la plupart utilisent typescript-eslint, existe-t-il une règle qui pourrait imposer que les valeurs doivent être vérifiées après l'indexation avant de pouvoir être utilisées? Cela peut être utile avant que le support de TS ne soit mis en œuvre.

Au cas où cela pourrait intéresser quelqu'un. Dans un commentaire précédent, j'ai mentionné que l'approche consistant à corriger la définition d'indexation Array dans lib.es5.d.ts avait un comportement étrange sur la déstructuration des tableaux car il semblait qu'elle n'était pas influencée par le changement. Il semble que cela dépende de l'option target dans tsconfig.json et cela fonctionne correctement lorsqu'il est es5 (https://github.com/microsoft/TypeScript/issues/37045 ).

Pour notre projet ce n'est pas un problème puisque nous utilisons l'option noEmit et la transpilation est gérée par meteor . Mais pour les projets où cela pourrait poser problème, une solution qui pourrait fonctionner est le safe-array-destructuring (https://github.com/typescript-eslint/typescript-eslint/pull/1645).
Il s'agit toujours d'un brouillon et cela pourrait ne pas être utile lorsque tout le problème sera résolu dans le compilateur TypeScript, mais si vous pensez que cela pourrait être utile, n'hésitez pas à répondre à toutes les préoccupations/améliorations de la règle typescript-eslint PR

Malheureusement, le correctif avec eslint dans votre PR ne prend en charge que les tuples et non les tableaux. Le problème principal et l'impact principal que j'ai avec cela sont dans la déstructuration des tableaux.

const example = (args: string[]) => {
  const [userID, nickname] = args
}

Je pense que toute cette question est allée d'un côté où je ne suis pas d'accord. Je ne pense pas qu'il devrait y avoir des contrôles obligatoires à l'intérieur de forEach, map etc... J'essaie également d'éviter toutes les utilisations de for lorsque cela est possible, donc les raisonnements pour éviter cela ont moins de sens pour moi. Néanmoins, je pense toujours que cela est crucial pour prendre en charge la déstructuration des tableaux.

Eh bien, il se trompe simplement pour toute déstructuration de tableau, vous êtes donc obligé de le faire en utilisant l'indexation.

N'est-ce pas si mal? La déstructuration des tableaux est une fonctionnalité étonnante. Le problème est que les frappes ne sont pas exactes. Désactiver la fonctionnalité n'est pas vraiment une bonne solution. L'utilisation d'index est à mon avis un code plus laid (plus difficile à lire et à comprendre) et plus sujet aux erreurs.

C'est plus moche, mais cela oblige le développeur à vérifier si l'élément est défini ou non. J'ai essayé de trouver des solutions plus élégantes, mais il semble que ce soit celle qui présente le moins d'inconvénients. Au moins pour notre cas d'utilisation.

@caseyhoward Comme indiqué précédemment dans ce numéro, cela provoque un comportement indésirable avec les différentes fonctions Array.prototype :

x.forEach( (i: string) => { ... } )  // Error because i has type string | undefined

Cela n'a pas besoin d'être corrigé dans les fonctions array.prototype. C'est un énorme problème pour la déstructuration des tableaux !

J'ai encore rencontré ça aujourd'hui. Je pense toujours que le bon compromis est celui-ci , sur lequel j'aimerais avoir des commentaires.

Un autre cas d'utilisation pour ce problème - j'ai rencontré ce problème lors de l'utilisation de typescript-eslint et de l'activation de no-unnecessary-condition . Dans le passé, lors de l'accès à un tableau par index pour effectuer une opération sur l'élément à cet index, nous utilisions un chaînage facultatif pour nous assurer que cet index est défini (au cas où l'index serait hors limites), comme dans array[i]?.doSomething() . Avec le problème décrit dans ce numéro, cependant, no-unnecessary-condition signale ce chaînage facultatif comme inutile (car le type n'est pas nullable selon le script dactylographié) et l'autofix supprime le chaînage facultatif, entraînant des erreurs d'exécution lorsque le tableau accède à l'index i est en

Sans cette fonctionnalité, mon application est devenue très boguée car je traite avec un tableau multidimensionnel, je dois toujours me rappeler d'accéder aux éléments en utilisant xs[i]?.[j] au lieu de xs[i][j] , aussi je dois explicitement lancer l'élément accédé comme const element = xs[i]?.[j] as Element | undefined pour assurer la sécurité du type.

La rencontre a été mon premier grand wtf dans Typescript. Le langage que j'ai trouvé par ailleurs merveilleusement fiable et cela m'a laissé tomber, et il est assez fastidieux d'ajouter "as T | undefined" aux accès aux tableaux. J'adorerais voir l'une des propositions mise en œuvre.

Oui, pareil ici. Cela nous a obligé à déployer nos propres types qui ont ( | undefined ) pour avoir la sécurité de type. Principalement pour l'accès aux objets (c'est probablement un problème ouvert séparé), mais la même logique s'applique à l'accès aux index de tableau.

Vous ne savez pas ce que vous voulez dire, pouvez-vous lier un problème ou montrer un exemple ?

Le mercredi 29 avril 2020 à 02h41 Kirill Groshkov [email protected]
a écrit:

Oui, pareil ici. Cela nous a obligé à déployer nos propres types qui ont ( |
undefined) pour avoir la sécurité de type. Principalement pour l'accès aux objets (c'est probablement un
problème ouvert séparé), mais la même logique s'applique à l'accès aux index de tableau.

-
Vous recevez ceci parce que vous avez commenté.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/microsoft/TypeScript/issues/13778#issuecomment-621096030 ,
ou se désinscrire
https://github.com/notifications/unsubscribe-auth/AAAGFJJT37N54I7EH2QLBYDRO7Y4NANCNFSM4C6KEKAA
.

@alangpierce

Une idée qui semble prometteuse : et si vous pouviez utiliser ?: lors de la définition d'une signature d'index pour indiquer que vous vous attendez à ce que des valeurs manquent parfois dans des conditions normales d'utilisation ? Il agirait comme | undefined mentionné ci-dessus, mais sans les inconvénients gênants. Il faudrait interdire les valeurs explicites undefined , ce qui, je suppose, est une différence par rapport aux ?: habituels.

C'est une idée intéressante qui va dans la bonne direction. Cependant, j'inverserais la logique : le cas par défaut devrait être que les signatures d'index incluent | undefined . Si vous voulez dire au compilateur que la casse non définie ne peut jamais apparaître dans votre code (donc vous n'accédez jamais à des clés invalides), vous pouvez ajouter !: à la signature, comme ceci :

type AlwaysDefined = {[key: string]!: string};
const t: AlwaysDefined = {};

t['foo'] // Has type string

et le cas par défaut sans !: ressemblerait à ce que nous connaissons, mais serait plus sûr :

type MaybeUndefined = {[key: string]: string};
const t: MaybeUndefined = {};

t['foo'] // Has type string | undefined

De cette façon, vous avez la sécurité par défaut et de la même manière vous n'avez pas à inclure explicitement undefined ce qui permettrait d'écrire undefined par accident.

Désolé si cela a déjà été suggéré, je n'ai pas pu lire tous les commentaires de ce fil.

En général, je pense vraiment que le comportement par défaut que nous avons maintenant n'est pas celui auquel les utilisateurs s'attendent lorsqu'ils utilisent TypeScript. Il vaut mieux prévenir que guérir, surtout lorsqu'une erreur peut être détectée aussi facilement que celle-ci par le compilateur.

Je n'arrivais pas à faire fonctionner index.d.ts avec mon projet et je ne voulais pas vraiment m'en occuper.

J'ai créé ce petit hack qui force un type guard :

 const firstNode = +!undefined && nodes[0];

Bien que le type se termine par : 0 | T , il fait toujours le travail.

const firstNode = nodes?.[0] fonctionnerait-il pas ?

@ricklove une raison pour laquelle vous avez préféré +!undefined à simplement 1 ?

const firstNode = nodes?.[0] fonctionnerait-il pas ?

Non, car le script dactylographié ne le traitera pas (à tort, à mon avis) comme facultatif (voir #35139).

Selon la documentation de Flow, le comportement avec les signatures d'index est le même qu'actuellement avec TypeScript :

Lorsqu'un type d'objet a une propriété d'indexation, les accès aux propriétés sont supposés avoir le type annoté, même si l'objet n'a pas de valeur dans cet emplacement au moment de l'exécution. Il est de la responsabilité du programmeur de s'assurer que l'accès est sûr, comme avec les tableaux.

var obj: { [number]: string } = {};
obj[42].length; // No type error, but will throw at runtime

Donc, dans ce souci, TS et Flow obligent l'utilisateur à penser à des clés non définies au lieu que le compilateur les aide :(

Ce serait un avantage de TS si le compilateur empêchait les utilisateurs de faire ce genre de bogues. En lisant tous les commentaires de ce fil, il semble que la grande majorité ici aimerait avoir cette fonctionnalité. L'équipe TS est-elle toujours à 100% contre ?

Il semble idiot que TS fasse de son mieux pour empêcher l'accès à des membres non définis, sauf celui-ci. C'est de loin le bogue le plus courant que j'ai corrigé dans notre base de code.

[déplacement de la discussion ici à partir de #38470]
Accepter tous les arguments expliquant pourquoi cela ne serait pas pratique pour les tableaux.
Je pense que cela devrait certainement être résolu pour Tuples:

// tuple 
var str = '';
var num = 100
var aa = [str, num] as const

// Awesome
var shouldBeString = aa[0] // string
var shouldBeNumber = aa[1] // number
var shouldError = aa[10000]; // type error

// Not so awesome 
var foo = aa[num] // string | number

pourquoi ne pas faire foo string | number | undefined ?

Donner cela aux tuples ne devrait pas affecter la majorité des utilisateurs, et les utilisateurs que cela affecte doivent avoir strictNullChecks activé et déclarer leurs tableaux comme des constantes...
Ils recherchent évidemment une sécurité de type plus stricte.

pourrait également aider avec ces

var notString1 = aa[Infinity]; // no error, not undefined
var notString2 = aa[NaN]; // no error, not undefined

Ce qui a initialement causé mon erreur d'exécution car un number est devenu un NaN , puis a renvoyé un undefined partir d'un tuple ..
Tout cela était de type sûr

Je ne suis pas sûr que les tuples soient vraiment si différents. Ils doivent s'adapter aux types de repos (par exemple [str, ...num[]] ), et techniquement aa[Infinity] est un Javascript parfaitement valide, donc je pouvais voir qu'il devenait compliqué de leur créer un cas spécial.

Votre message m'a également fait réfléchir à ce à quoi cela ressemblerait si nous obtenions de l'aide pour traiter les retours d'index comme non définis. Si, comme dans votre exemple, aa[num] en fait été tapé comme string | number | undefined , devrais-je écrire for (let i = 0; i < 2; i++) { foo(aa[i]!); } même si je sais que l'index restera dans les limites ? Pour moi, lorsque des vérifications nulles strictes signalent quelque chose que j'ai écrit, j'aime pouvoir le corriger en tapant mieux les choses en premier lieu, ou en utilisant des gardes d'exécution - si je dois recourir à une assertion non nulle, je le considérer comme un échec de ma part. Je ne vois pas comment contourner cela dans ce cas, cependant, et cela me dérange.

Si tuple[number] toujours tapé sur T | undefined , comment voudriez-vous gérer le cas où vous savez que l'index est correctement borné ?

@thw0rted Je ne me souviens pas de la dernière fois que j'ai utilisé une boucle "classique" for dans Typescript ( .map et .reduce ftw). C'est pourquoi une option de compilateur pour cela serait géniale imo (qui peut être désactivée par défaut). De nombreux projets ne rencontrent jamais quelque chose comme le cas que vous avez fourni et n'utilisent que des signatures d'index pour la déstructuration de tableaux, etc.

(mon commentaire original : https://github.com/microsoft/TypeScript/issues/13778#issuecomment-517759210)

Oh, en toute honnêteté, j'indexe aussi rarement avec une boucle for. J'utilise largement Object.entries ou Array#map , mais de temps en temps, j'ai besoin de passer des clés pour indexer dans un objet ou (probablement encore moins souvent) des index dans un tableau ou un tuple. La boucle for était un exemple artificiel, certes, mais elle soulève le fait que l'une ou l'autre option ( undefined ou non) a des inconvénients.

mais cela soulève le point que l'une ou l'autre option ( undefined ou non) a des inconvénients.

Ouais et ce serait bien si l'utilisateur de TypeScript pouvait choisir quel inconvénient il préfère :wink:

Oh, en toute honnêteté, j'indexe aussi rarement avec une boucle for. J'utilise largement Object.entries ou Array#map , mais de temps en temps, j'ai besoin de passer des clés pour indexer dans un objet ou (probablement encore moins souvent) des index dans un tableau ou un tuple.

Dans les cas où vous en avez besoin, vous pouvez utiliser Array#entries :

for (const [index, foo] of array.entries()) {
    bar(index, foo)
}

Personnellement, je n'utilise pas beaucoup Array#forEach , et je n'utilise jamais Array#map pour l'itération (je l'utilise tout le temps pour le mappage cependant). Je préfère garder mon code plat et j'apprécie la possibilité de sortir d'une boucle for...of.

Je ne suis pas sûr que les tuples soient vraiment si différents. Ils doivent s'adapter aux types de repos (par exemple [str, ...num[]] ), et techniquement aa[Infinity] est un Javascript parfaitement valide, donc je pouvais voir qu'il devenait compliqué de leur créer un cas spécial.

Votre message m'a également fait réfléchir à ce à quoi cela ressemblerait si nous obtenions de l'aide pour traiter les retours d'index comme non définis. Si, comme dans votre exemple, aa[num] en fait été tapé comme string | number | undefined , devrais-je écrire for (let i = 0; i < 2; i++) { foo(aa[i]!); } même si je sais que l'index restera dans les limites ? Pour moi, lorsque des vérifications nulles strictes signalent quelque chose que j'ai écrit, j'aime pouvoir le corriger en tapant mieux les choses en premier lieu, ou en utilisant des gardes d'exécution - si je dois recourir à une assertion non nulle, je le considérer comme un échec de ma part. Je ne vois pas comment contourner cela dans ce cas, cependant, et cela me dérange.

Si tuple[number] toujours tapé sur T | undefined , comment voudriez-vous gérer le cas où vous savez que l'index est correctement borné ?

Je ne suis pas sûr qu'il y ait autant de for(let i..) itération sur les tuples..
Si le tuple a un seul type, l'utilisateur utilisera probablement un tableau,

D'après mon expérience, les tuples sont parfaits pour les types mixtes et si c'est le cas général, vous vérifiez quand même le type

var str = '';
var num = 100
var aa = [str, num] as const
for (let i = 0; i < aa.length; i++) {
 aa[i] // needs some type check anyway to determine if 'string' or 'number'
}

En supposant également que ce changement proposé se produise, qu'est-ce qui empêche les gens de le faire ?

// this sucks
for (let i = 0; i < 2; i++) { 
   foo(aa[i]!); // ! required
}

// this would work
for (let i = 0 as 0 | 1; i < 2; i++) { 
   foo(aa[i]); //  ! not required 
}

vous pouvez utiliser keyof typeof aa au lieu de 0 | 1 quand ils résolvent ce problème

Bien sûr, il n'y aurait aucune sécurité de type lors i arithmétique
mais cela permet vraiment aux gens de choisir cela pour la sécurité du type par rapport au tableau par défaut "Pas strictement de type sûr mais pratique"

Je pense qu'avoir les opérateurs ?? et ?. signifie qu'il n'est plus trop encombrant de faire un accès aléatoire aux Array s.
Mais aussi, il est probablement beaucoup plus fréquent de

  • itérer (par exemple avec .map ou .forEach , où il n'y a pas besoin de " T | undefined ", le rappel n'est jamais exécuté aux indices hors limites) ou
  • parcourez les Array en boucles, dont certaines pourraient être statiquement déterminées comme sûres ( for...of .. presque, sauf pour _sparse arrays_, for...in je pense que c'est sûr).

Et aussi, une variable peut être considérée comme un "index sûr" si elle est vérifiée avec <index> in <array> premier.


_Clarification_ : Je parle de la raison initiale pour laquelle Array<T> n'avait pas le type d'index [number]: T | undefined (mais juste [number]: T ), qui était qu'il serait trop lourd à utiliser.
Je veux dire que ce n'est plus le cas, donc undefined pourraient être ajoutés.

@vp2177 Le problème ce ne sont pas les fonctionnalités (oui, ?. fonctionne), le problème est que le compilateur ne nous avertit pas à ce sujet et nous empêche de nous tirer une balle dans le pied.

Peut-être qu'une règle de peluche pourrait le faire, mais quand même.

oui, ?. fonctionne

Je ne suis pas d'accord. ?. fonctionnera pour accéder aux propriétés de la valeur indexée, mais le compilateur dira toujours que la valeur est définie, qu'elle le soit ou non (voir #35139).

Pour ajouter à cela, si vous utilisez eslint et que vous souhaitez utiliser no-unnecessary-condition , ces accès seront signalés comme inutiles et supprimés car le compilateur dit qu'il n'est jamais indéfini et que cela rendrait ?. inutile.

@martpie Désolé, je pense que vous avez mal compris mon commentaire, j'ai ajouté une clarification.

Je veux dire... semble être une bonne idée d'avoir un --strictIndexChecks ou similaire qui permet aux gens d'opter pour ce type de comportement. Ce ne sera alors pas un changement décisif pour tout le monde et cela donnera un environnement de type plus strict.

Je pense que Flow fonctionne de cette façon par défaut pour les index et n'a pas les problèmes susmentionnés, mais cela fait un moment que je ne l'ai pas utilisé, donc je peux me tromper.

@bradennapier Flow n'utilise pas non plus T | undefined dans la signature d'index pour Array s (donc identique à TypeScript, malheureusement.)

J'ai déjà suggéré une approche qui pourrait rendre tout le monde heureux. J'espère que vous pouvez le répéter en d'autres termes.

Fondamentalement, le comportement par défaut de la signature d'index serait modifié en

  • Inclure | undefined lors de la lecture à partir d'un tableau ou d'un objet
  • N'incluez pas | undefined lors de l'écriture (car nous ne voulons que des valeurs T dans notre objet)

La définition serait comme ça :

type MaybeUndefined = {[key: string]: string};
const t: MaybeUndefined = {};

const x = t['foo'] // Has type string | undefined
t['foo'] = undefined // ERROR! 
t['foo'] = "test" // Ok

Pour les personnes qui ne souhaitent pas inclure undefined dans le type, elles peuvent soit le désactiver à l'aide d'une option du compilateur, soit le désactiver uniquement pour certains types à l'aide de l'opérateur ! :

type AlwaysDefined = {[key: string]!: string};
const t: AlwaysDefined = {};

const x = t['foo'] // Has type string
t['foo'] = undefined // ERROR! 
t['foo'] = "test" // Ok

Pour ne pas casser le code existant, il peut également être préférable d'introduire une nouvelle option de compilateur qui entraîne l'inclusion de undefined (comme indiqué avec le type MaybeUndefined ci-dessus). Si cette option n'est pas spécifiée, tous les types de signature d'index se comportent comme si l'opérateur ! était utilisé dans la déclaration.

Une autre idée qui surgit : on peut vouloir utiliser le même type à certains endroits dans le code avec les deux comportements différents (inclure undefined ou non). Un nouveau mappage de type pourrait être défini :

type MakeDefined<T> = {[K in keyof T]!: T[K]}

Serait-ce une bonne extension de TypeScript ?

J'aime l'idée mais je soupçonne que nous devrions d'abord obtenir https://github.com/microsoft/TypeScript/issues/2521 - ce qui, ne vous méprenez pas, je pense toujours que nous devrions absolument, mais je ' Je ne retiens pas mon souffle.

@bradennapier Flow n'utilise pas non plus T | undefined dans la signature d'index pour Array s (donc identique à TypeScript, malheureusement.)

Hmm qu'en est-il des index d'objets ? Je n'ai jamais eu besoin de tableaux, mais je suis assez certain que cela vous permet de vérifier lorsque vous utilisez des objets.

@bradennapier Index d'objets ? Comme dans o[4]o est le type object ? TypeScript ne vous permet pas de faire cela.....

l'expression de type '4' ne peut pas être utilisée pour indexer le type '{}'
La propriété '4' n'existe pas sur le type '{}'.ts(7053)

@RDGthree ne doit pas être un chevalier blanc pour Microsoft de toutes choses, mais vous avez apparemment manqué ce morceau de mhegazy dans la première réponse :

À l'exception de strictNullChecks, nous n'avons pas d'indicateurs qui modifient le comportement du système de type. Les indicateurs activent/désactivent généralement le rapport d'erreurs.

et un peu plus tard RyanCavanaugh a :

Nous restons assez sceptiques quant au fait que quiconque tirerait profit de ce drapeau dans la pratique. Les cartes et les éléments similaires à des cartes peuvent déjà s'inscrire | indéfini sur leurs sites de définition, et l'application d'un comportement de type CLUF sur l'accès aux baies ne semble pas être une victoire. Nous aurions probablement besoin d'améliorer considérablement le CFA et les gardes de type pour rendre cela acceptable.

Donc, fondamentalement - ils sont parfaitement conscients du fait qu'un drapeau est demandé, et jusqu'à présent, ils n'ont pas été convaincus que cela en valait la peine pour eux pour le bénéfice plutôt douteux qui a été suggéré que les gens en retireraient. Honnêtement, je ne suis pas surpris, puisque tout le monde revient ici avec à peu près exactement les mêmes suggestions et exemples qui ont déjà été abordés. Idéalement, vous devriez juste utiliser for of ou des méthodes pour les tableaux, et Map au lieu d'objets (ou moins efficacement, les valeurs d'objet 'T | undefined').

Je suis ici parce que je maintiens un package DT populaire et que les gens continuent de demander que |undefined soient ajoutés à des choses comme les cartes d'en-tête http, ce qui est tout à fait raisonnable, à l'exception du bit où cela casserait à peu près toutes les utilisations existantes , et le fait que cela rend d'autres utilisations sûres comme Object.entries() bien pires à utiliser. À part se plaindre du fait que votre opinion est meilleure que celle des créateurs de Typescript, quelle est réellement votre contribution ici ?

Simon, j'ai peut-être mal lu votre commentaire, mais cela ressemble en quelque sorte à un argument en faveur de la suggestion originale. La capacité manquante du "dernier kilomètre" consiste à traiter les propriétés comme étant parfois | undefined , selon le contexte, mais pas dans d'autres cas. C'est pourquoi j'ai fait une analogie avec #2521 upthread.

Dans un scénario idéal, je pourrais déclarer un tableau ou un objet tel que, étant donné

ts const arr: Array<T>; const n: number; const obj: {[k: K]: V}; const k: K;

Je peux en quelque sorte me retrouver avec

  • arr[n] types comme T | undefined
  • arr[n] = undefined est une erreur
  • J'ai accès à une sorte d'itération sur arr qui me donne des valeurs tapées comme T , pas T | undefined
  • obj[k] types comme V | undefined
  • obj[k] = undefined est une erreur
  • Object.entries() par exemple devrait me donner des tuples de [K, V] et rien ne peut être indéfini

C'est la nature asymétrique du problème qui définit cette question à mon avis.

Je dirais que l'affirmation selon laquelle c'est un problème pour les développeurs qui utilisent des boucles for ne prend pas en compte les développeurs qui utilisent la déstructuration des tableaux. Il est cassé dans Typescript et ne sera pas corrigé non plus à cause de ce problème. Il fournit des frappes inexactes. La déstructuration des tableaux IMO effectuée est un problème beaucoup plus important que ceux qui utilisent encore des boucles for.

const example = (args: string[]) => {
  const [userID, duration, ...reason] = args
  // userID and duration is AUTOMATICALLy inferred to be a string here. 
 // However, if for whatever reason args is an empty array 
// userID is actually `undefined` and NOT a `string`. 

// This is valid but it should not be because userID could be undefined
userID.toUpperCase()
}

Je ne veux pas m'éloigner trop du point d'origine du problème, mais vous devriez vraiment déclarer cette méthode d'exemple en utilisant un type de tuple pour l'argument de fonction. Si la fonction était déclarée comme ([userId, duration, ...reason]: [string, number, ...string[]]) => {} vous n'auriez pas à vous en soucier en premier lieu.

Je veux dire, bien sûr, je pourrais juste le lancer, mais ce n'est pas vraiment une solution. De même, n'importe qui ici pourrait simplement lancer l'indexation du tableau de boucle for et rendre tout ce problème muet.

for (let i = 0; i < array.length; i++) {
  const value = array[i] as string | undefined
}

Le problème est que Typescript n'avertit pas de ce comportement. En tant que développeur, nous devons nous rappeler de les convertir manuellement comme indéfinis, ce qui est un mauvais comportement pour un outil qui cause plus de travail aux développeurs. Pour être juste, le casting devrait en fait ressembler à ceci :

const example = (args: [string | undefined, string | undefined, ...string[] | ...undefined[]]) => {

}

C'est pas sympa du tout. Mais comme je l'ai dit ci-dessus, le problème principal n'est même pas besoin de le taper comme ça, c'est que TS n'avertit pas du tout pour cela. Cela conduit les développeurs qui oublient cela pour pousser le code, cool CI n'a trouvé aucun problème lors de la fusion et du déploiement de tsc. TS donne un sentiment de confiance dans le code et le fait d'avoir des frappes inexactes donne un faux sentiment de confiance. => Erreurs à l'exécution !

Ce n'est pas un "cast", c'est taper correctement vos paramètres formels. Mon point était que si vous tapez votre méthode correctement, alors toute personne qui l'appelait de manière non sécurisée avec l'ancienne signature ( args: string[] ) obtiendra désormais des erreurs au moment de la compilation, comme ils le devraient , quand ils ne sont pas garantis de passer le bon nombre d'arguments. Si vous voulez continuer à laisser les gens s'en tirer en ne transmettant pas tous les arguments dont vous avez besoin et en les vérifiant vous-même au moment de l'exécution, il est en fait très facile d'écrire sous la forme (args: [string?, number?, ...string[]]) . Cela fonctionne déjà très bien et n'est pas vraiment pertinent pour ce problème.

Cela déplacerait simplement le problème de cette fonction vers une autre fonction qui appelle cette fonction. Comme vous devez toujours analyser la chaîne fournie par les utilisateurs en valeurs distinctes et si vous utilisez la déstructuration de tableau pour l'analyser à l'intérieur du commandHandler qui appelle cette fonction, vous vous retrouvez avec le même problème.

Il n'y a aucun moyen d'avoir des saisies inexactes fournies pour la déstructuration des tableaux.

Je ne veux pas m'éloigner trop du point d'origine du problème

Remarque : bien que je sois d'accord avec vous, ils doivent être traités comme des problèmes distincts, chaque fois que quelqu'un ouvre un problème montrant les problèmes de déstructuration du tableau, il est fermé et les utilisateurs sont transférés ici. #38259 #36635 #38287 etc... Donc je ne pense pas qu'on s'éloigne du sujet de ce numéro.

Certaines de mes frustrations avec ce problème seraient résolues par une syntaxe simple pour appliquer | undefined au type qui serait autrement déduit de l'accès à l'objet. Par exemple:

const complexObject: { [key: string]: ComplexType } = { ... };
const maybeKey = 'two';

// From:
const maybeValue = complexObject[maybeKey] as
  | (Some<Complex<Type>> & Pick<WithPlentyOf, 'Utility'> & Types)
  | undefined;

// To:
const maybeValue2 = complexObject[maybeKey] as ?;

Une vérification de type plus stricte reste donc opt-in. Ce qui suit fonctionne mais nécessite une duplication (et ajoute rapidement une quantité similaire de bruit visuel) :

const maybeValue2 = complexObject[maybeKey] as
  | typeof complexObject[typeof maybeKey]
  | undefined;

J'imagine que ce type de syntaxe "type assert to may" ( as ? ) faciliterait également la mise en œuvre d'une règle eslint require-safe-index-signature-access qui n'est pas très déroutante pour les nouveaux utilisateurs. (Par exemple, "Vous pouvez corriger l'erreur de charpie en ajoutant as ? à la fin.") La règle pourrait même inclure un autofixeur sûr car il ne pouvait que provoquer l'arrêt de la compilation, jamais de problèmes d'exécution.

function eh<T>(v: T): T | undefined {
    return v;
}

const arr = [0, 1, 2, 3, 4, 5];
const thing1 = arr[1]; // number
const thing2 = eh(arr[1]); // number | undefined

Je ne pense pas qu'il y ait une seule application de cela à part cette fonctionnalité surprise de TS bien que hehe

function eh<T>(v: T): T | undefined {
    return v;
}

const arr = [0, 1, 2, 3, 4, 5];
const thing1 = arr[1]; // number
const thing2 = eh(arr[1]); // number | undefined

Merci pour la réponse. Alors qu'une fonction comme celle-ci fonctionnerait (et sera presque certainement optimisée par la plupart des moteurs JS), je préfère accepter le bruit de syntaxe supplémentaire mentionné ( as ComplexType | undefined ) que de laisser le fichier JS compilé conserver cette méthode "wrapper " partout où il est utilisé.

Une autre option de syntaxe (qui serait moins spécifique au cas d'utilisation que le as ? ci-dessus) pourrait être d'autoriser l'utilisation du mot-clé infer dans les assertions de type comme espace réservé pour le type autrement déduit, par exemple:

const maybeValue = complexObject[maybeKey] as infer | undefined;

Actuellement, les déclarations infer ne sont autorisées que dans la clause extends d'un type conditionnel ( ts(1338) ), donnant ainsi au mot clé infer une signification dans le contexte de as [...] Les assertions de type

Cela pourrait toujours être raisonnable à appliquer en utilisant infer | undefined via une règle eslint tout en étant assez général pour d'autres cas, par exemple où le type résultant peut être affiné avec des types utilitaires :

// Strange example, maybe from an unusual compiler originally written in JS:
if(someRegularExpression.test(maybeKey)) {
  /**
  * If we are inside this code block and this `maybeKey` exists on `partialObject`, 
  * we know its `regexSuccess` must also be defined, though this knowledge 
  * cannot be encoded using TypeScript's type system.
  */
  const maybeValue = partialObject[maybeKey] as (Required<Pick<infer, 'regexSuccess'>> & infer) | undefined;
  // ...
}
// Here we don't know if `regexSuccess` is defined on `maybeValue2`:
const maybevalue2 = partialObject[maybeKey] as infer | undefined;
function eh<T>(v: T): T | undefined {
    return v;
}

const arr = [0, 1, 2, 3, 4, 5];
const thing1 = arr[1]; // number
const thing2 = eh(arr[1]); // number | undefined

C'est inutile si nous devons continuer à nous rappeler d'utiliser de toute façon un modèle différent et plus sûr. Le but est que TypeScript nous protège, que nous soyons dans une très bonne journée, fatigués ou novices dans une base de code.

Notre comportement actuel est que l'expression de type Array<string>[number] est interprétée comme "Le type qui se produit lorsque vous indexez un tableau par un nombre". Vous avez le droit d'écrire

const t: Array<string>[number] = "hello";

comme moyen détourné d'écrire const t: string .

Si ce drapeau existait, que se passerait-il ?

Si Array<string>[number] vaut string | undefined , alors vous avez un problème de solidité sur les écritures :

function write<T extends Array<unknown>>(arr: T, v: T[number]) {
    arr[0] = v;
}
const arr = ["a", "b", "c"];
// Would be OK
write(arr, undefined);

Si Array<string>[number] est string , alors vous avez le même problème que celui décrit dans les lectures :

function read<T extends Array<unknown>>(arr: T): T[number] {
    return arr[14];
}
const arr = ["a", "b", "c"];
// Would be OK
const k: string = read(arr);

Il semble trop complexe d'ajouter une syntaxe de type à la fois pour "Le type de lecture d'index de" et "Le type d'écriture d'index de". Les pensées?

Je dois ajouter que changer la signification de Array<string>[number] est vraiment problématique car cela impliquerait que ce drapeau change l'interprétation des fichiers de déclaration. C'est angoissant parce que cela semble être le genre d'indicateur qu'un nombre substantiel de personnes permettrait, mais pourrait provoquer des erreurs / ne pas se produire dans les choses publiées à partir de DefinitelyTyped, nous devrons donc probablement nous assurer que tout se construit proprement dans les deux sens. Le nombre de drapeaux que nous pouvons ajouter avec ce comportement est évidemment extrêmement petit (nous ne pouvons pas tester par CI 64 configurations différentes de chaque type de package) donc je pense que nous devrions laisser Array<T>[number] comme T juste pour éviter ce problème.

@RyanCavanaugh Dans votre exemple avec write(arr, undefined) , l'appel à write serait accepté, mais l'affectation arr[0] = v; ne serait-elle plus compilée ?

Si la fonction write ci-dessus provenait d'une bibliothèque et était du JS compilé sans le drapeau, alors sûr que ce serait malsain, cependant, c'est déjà un problème existant car les bibliothèques peuvent être compilées avec les drapeaux qu'elles choisissent. Si une bibliothèque a été compilée sans vérifications nulles strictes, une fonction qu'elle contient pourrait renvoyer string , alors qu'elle devrait en réalité renvoyer string|undefined . Mais de nos jours, la plupart des bibliothèques semblent implémenter des contrôles nuls, sinon les gens se plaignent. De même, une fois que ce nouvel indicateur existe, nous espérons que les bibliothèques commenceront à l'implémenter et que la plupart des bibliothèques finiront par l'avoir défini.

De plus, bien que je ne trouve pas d'exemples concrets dans ma tête, j'ai certainement dû définir skipLibCheck pour que mon application soit compilée. C'est une option tsconfig standard dans notre passe-partout lors de la création d'un nouveau référentiel TS, car nous avons eu tellement de problèmes avec.

Personnellement (et peut-être égoïstement), je suis prêt à prendre un coup sur les cas extrêmes, si cela rend _mon_ code généralement plus sûr. Je crois toujours que je rencontrerais des pointeurs nuls en accédant aux tableaux par index plus souvent que je ne rencontrerais d'autres cas extrêmes, bien que je puisse être convaincu du contraire.

Si tableau[nombre] est une chaîne | undefined, alors vous avez un problème de solidité sur les écritures :

À mon avis, Array<string>[number] devrait toujours être string|undefined . C'est la réalité, si j'indexe dans un tableau avec n'importe quel nombre, j'obtiendrai le type d'élément du tableau ou non défini. Vous ne pouvez pas vraiment être plus précis à moins d'encoder la longueur du tableau comme vous le feriez avec des tuples. Votre exemple d'écriture ne taperait pas check, car vous ne pouvez pas affecter string|undefined à un index de tableau.

Il semble trop complexe d'ajouter une syntaxe de type à la fois pour "Le type de lecture d'index de" et "Le type d'écriture d'index de". Les pensées?

Cela semble être exactement ce qui devrait être car ce sont deux choses différentes. Aucun tableau n'aura jamais d'index défini, vous aurez donc toujours le potentiel d'être indéfini. Le type de Array<string>[number] serait string|undefined . Afin de spécifier ce que vous voulez comme T dans un Array<T> , un type d'utilitaire peut être utilisé (nommage pas génial): ArrayItemType<Array<string>> = string . Cela n'aide pas avec les types Record , qui peuvent nécessiter quelque chose comme RecordValue<Record<string, number>, string> = string .

Je suis d'accord qu'il n'y a pas de bonnes solutions ici, mais je suis à peu près sûr que je préférerais la solidité des lectures d'index.

Je ne suis pas particulièrement convaincu de la nécessité de cela sur les tableaux, car de nombreux autres langages (y compris les langages "sûrs" comme Rust) laissent le soin de vérifier les limites à l'utilisateur, et donc moi et de nombreux développeurs sommes déjà habitués à le faire . De plus, la syntaxe a tendance à le rendre assez évident dans ces cas, car ils utilisent _toujours_ une notation entre crochets comme foo[i] .

Cela dit, j'ai vraiment envie d'ajouter cela aux objets indexés par chaîne, car il est très difficile de dire si la signature de foo.bar est correcte (en raison du champ bar explicitement défini), ou éventuellement undefined (car il fait partie d'une signature d'index). Si ce cas (qui, je pense, a une valeur assez élevée) est résolu, alors le cas du tableau devient probablement trivial et vaut probablement la peine d'être fait aussi.


Il semble trop complexe d'ajouter une syntaxe de type à la fois pour "Le type de lecture d'index de" et "Le type d'écriture d'index de". Les pensées?

La syntaxe getter et setter de javascript pourrait être étendue aux définitions de type. Par exemple, la syntaxe suivante est parfaitement comprise par TypeScript :

const foo = {
  _bar: "",
  get bar(): string {
    return this._bar;
  },
  set bar(value: string) {
    this._bar = value;
  }
}

Cependant, TS les "fusionne" actuellement en un seul type, car il ne suit pas les types "get" et "set" d'un champ séparément. Cette approche fonctionne pour la plupart des cas courants, mais a ses propres inconvénients, comme exiger que les getters et les setters partagent le même type, et attribuer de manière incorrecte un type getter à un champ qui définit uniquement un setter.

Si TS devait suivre "get" et "set" séparément pour tous les champs (ce qui a probablement des coûts de performances réels), cela résoudrait ces problèmes et fournirait également un mécanisme pour décrire la fonctionnalité décrite dans ce numéro :

type foo = {
  [key: string]: string
}

deviendrait essentiellement un raccourci pour :

type foo = {
  get [key: string](): string | undefined;
  set [key: string](string): string;
}

Je devrais ajouter que changer le sens de Array[nombre] est vraiment problématique car cela impliquerait que ce drapeau change l'interprétation des fichiers de déclaration.

Si les fichiers de déclaration générés utilisaient toujours la syntaxe complète getter+setter, ce serait _uniquement_ un problème pour ceux écrits à la main. L'application des règles actuelles à la syntaxe abrégée restante _dans les fichiers de définition uniquement_ pourrait être une solution ici. Cela résoudrait certainement les problèmes de compatibilité, mais augmenterait également le coût mental de la lecture .ts fichiers .d.ts .


De côté:

Maintenant, bien sûr, cela ne résout pas _toutes_ les subtiles mises en garde avec les getters/setters :

foo.bar = "hello"

// TS assumes that bar is now a string, which technically isn't guaranteed when
// custom setters and getters are used.
const result: string = foo.bar

Il s'agit d'un autre cas limite, et il vaut probablement la peine d'examiner s'il entre dans le cadre des objectifs de TS ... mais de toute façon, il pourrait probablement être résolu séparément, car ma proposition ici est cohérente avec le comportement actuel de TypeScript.

J'ai vraiment l'impression que je dois juste faire apparaître toutes les deux pages de commentaires et laisser à nouveau un lien vers # 2521.

Il semble trop complexe d'ajouter une syntaxe de type à la fois pour "Le type de lecture d'index de" et "Le type d'écriture d'index de". Les pensées?

Nan! Et au moins (clics lien...) 116 autres ici sont d'accord avec moi. Je serais si heureux d'avoir au moins la possibilité de taper lectures et écritures différemment. Ceci n'est qu'un autre excellent cas d'utilisation de cette fonctionnalité.

Je serais _tellement heureux_ d'avoir au moins la possibilité de taper reads et writes différemment. Ceci est juste _encore un_ excellent cas d'utilisation pour cette fonctionnalité.

Je crois que l'intention était de se demander s'il devrait y avoir un moyen de faire référence aux types de lecture et d'écriture, par exemple, devrait-il y avoir quelque chose comme Array<string>[number]= pour faire référence au type d'écriture ?

Je serais heureux de la résolution de #2521 et de ce problème si deux choses se produisent :

  • Tout d'abord, tapez différemment les getters et les setters, selon #2521
  • Ensuite, créez un indicateur comme indiqué dans ce numéro, de sorte que le "type d'écriture" de Array<string>[number] soit string tandis que le "type de lecture" est string | undefined .

Je suppose (à tort ?) que le premier jetterait les bases du second, voir https://github.com/microsoft/TypeScript/issues/13778#issuecomment -630770947. Je n'ai pas particulièrement besoin d'une syntaxe explicite pour définir moi-même le "type d'écriture".

@RyanCavanaugh méta-question rapide : serait-il plus utile d'avoir ma suggestion plutôt spécifique ci-dessus en tant que problème distinct dans le but de débattre et de suivre plus clairement ses forces/faiblesses/coûts (d'autant plus que les coûts de performance ne seront pas négligeables) ? Ou est-ce que cela ne ferait qu'ajouter au bruit ?

@RyanCavanaugh

const t: Array<string>[number] = "hello";

comme moyen détourné d'écrire const t: string .

Je suppose que c'est le même raisonnement pour les tuples?

const t : [string][number] = 'hello' // const t: string

cependant, les tuples sont conscients des limites et renvoient correctement undefined pour des types de nombres plus spécifiques :

const t0: [string][0] = 'hello' // ok
const t1: [string][1] = 'hello' // Type '"hello"' is not assignable to type 'undefined'.

// therefore this is already true!
const t2: [string][0|1] // string | undefined 

pourquoi taper number agirait-il différemment ?

Il semble que vous ayez les mécanismes en place pour le faire : https://github.com/microsoft/TypeScript/issues/38779

Cela répondrait à mes besoins sans ajouter de drapeau supplémentaire.

Au cas où cela aiderait quelqu'un, voici un plugin ESLint qui signale l'accès non sécurisé aux tableaux et la déstructuration des tableaux/objets. Il ne signale pas les utilisations sûres comme les tuples et les objets (non enregistrés).

https://github.com/danielnixon/eslint-plugin-total-functions

Bravo, mais j'espère qu'il est clair que cela doit encore être ajouté au niveau de la langue car c'est purement dans le domaine de la vérification de type et certains projets n'ont pas de linter ou les bonnes règles.

@RyanCavanaugh

nous avons également testé à quoi ressemble cette fonctionnalité dans la pratique et je peux vous dire que ce n'est pas joli

Juste curieux, pouvez-vous nous dire de quoi parle la partie pas jolie ? Peut-être que nous pouvons régler le problème si vous pouvez en dire plus à ce sujet.

Cela ne devrait pas être une option, cela devrait être la valeur par défaut .

Autrement dit, si le système de types de TypeScript doit décrire au moment de la compilation les types que les valeurs JavaScript peuvent prendre au moment de l'exécution :

  • indexer un Array peut bien sûr retourner undefined en JavaScript (index hors limites ou tableau clairsemé), donc la signature de lecture correcte serait T | undefined .
  • "faire en sorte qu'un index dans un Array soit undefined est également possible via le mot-clé delete .

Comme TypeScript 4 empêche

const a = [1, 2]
delete a[1]

il y a de bonnes raisons d'empêcher également a[1] = undefined .
Cela suggère que Array<T>[number] devrait en effet être différent pour les lectures et les écritures.

Cela pourrait être "complexe", mais cela permettrait de modéliser plus précisément les possibilités d'exécution en JavaScript ;
C'est pourquoi TypeScript a été _fait pour_, n'est-ce pas ?

Il ne sert à rien de discuter de l'idée de faire en sorte que | undefined renvoie le comportement par défaut - ils ne publieront

Je suis tout de même d'accord avec le reste du post.

Il ne sert à rien de discuter de l'idée de faire en sorte que | undefined renvoie le comportement par défaut - ils ne publieront _jamais_ une version de Typescript qui explose simplement des millions de lignes de code hérité en raison d'une mise à jour du compilateur. Un changement comme celui-là serait le changement le plus marquant de tous les projets que j'ai jamais suivis.

Je suis tout de même d'accord avec le reste du post.

Pas si c'est une option de configuration.

Pas si c'est une option de configuration.

C'est juste, mais à tout le moins, je m'attendrais à une longue période de "préparation" où il est désactivé par défaut, ce qui donne aux gens beaucoup de temps (6 mois ? un an ? plus ?) pour convertir le code hérité avant qu'il ne devienne activé. -défaut. Comme, ajoutez le drapeau dans la v4.0, réglez-le sur activé par défaut dans la v5.0, ce genre de chose.

Mais c'est dans un avenir

Il ne sert à rien de discuter de l'idée de faire en sorte que | undefined renvoie le comportement par défaut - ils ne publieront _jamais_ une version de Typescript qui explose simplement des millions de lignes de code hérité en raison d'une mise à jour du compilateur. Un changement comme celui-là serait le changement le plus marquant de tous les projets que j'ai jamais suivis.

Je suis tout de même d'accord avec le reste du post.

À votre avis @thw0rted, j'ai proposé un compromis ici : https://github.com/microsoft/TypeScript/issues/38779

Il permet cette fonctionnalité, dans une fonctionnalité qui a été introduite beaucoup plus tard, qui n'est toujours pas aussi largement utilisée, et il semble qu'elle soit déjà "presque" là et qu'elle n'ait besoin que de modifications mineures.

Pour la cohérence de "strict" quelque chose de Array<T> doit être considéré comme "a list of type T that is infinitly long with no gaps" Chaque fois que cela n'est pas souhaité, vous devez utiliser des tuples.

Oui, il y a des inconvénients à cela, mais encore une fois, j'ai l'impression que c'est un "compromis" juste entre cette fonctionnalité qui n'est jamais implémentée et implémentée exactement comme nous le demandons ici.

Si vous êtes d'accord, veuillez voter

Je ne suis pas d'accord pour dire que cela nécessite une toute nouvelle option/configuration.

Je ne pense pas que cela devrait être le comportement par défaut lorsque l'option stricte est désactivée, mais lorsque j'active l'option stricte SOYEZ STRICT et les applique même sur des projets plus anciens. Activer strict est ce que les gens optent pour les frappes les plus strictes/précises possibles. Si j'active une option stricte, j'aimerais avoir confiance en mon code et c'est la meilleure partie de TS. Mais ces frappes inexactes, même avec strict activé, ne sont qu'un casse-tête.

Dans l'intérêt de garder ce fil lisible, j'ai caché un certain nombre de commentaires tangentiels qui n'ajoutaient pas de manière significative à la conversation. En général, vous pouvez supposer que des commentaires dans la veine suivante ont déjà été faits dans les 212 commentaires précédents et n'ont pas besoin d'être répétés :

  • TypeScript devrait introduire pour la toute première fois un changement radicalement important dans le comportement de vérification. du projet avant de venir ici pour plaider en faveur de changements sans précédent
  • Arguments fondés sur l'idée que personne n'a pris en considération le fait que le drapeau de configuration existe - nous sommes conscients que mettre des choses derrière un drapeau signifie qu'ils n'ont pas besoin d'être activés par défaut, ce n'est pas le premier rodéo de quiconque
  • Quel est le retard, faites-le déjà ! - ben maintenant que tu le dis comme ça j'en suis convaincu

Il y a de très vrais défis pratiques et techniques qui doivent être résolus ici et il est décevant que nous ne puissions pas faire un travail significatif dans ce fil car il est rempli de gens qui viennent suggérer que tout ce qui se cache derrière un drapeau n'a pas de conséquences réelles ou que toute erreur qui peut be ts-ignore 'd n'est pas un fardeau de mise à niveau.

@RyanCavanaugh

merci pour ton résumé.

veuillez vous impliquer davantage dans les objectifs de conception à long terme du projet avant de venir ici pour plaider en faveur de changements sans précédent

J'ai supprimé mon commentaire et si vous le souhaitez, je peux également supprimer ce commentaire. Mais comment comprendre le premier objectif du projet :

1. Identifiez de manière statique les constructions susceptibles d'être des erreurs.

(Source : https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals)

Je ne comprends pas votre commentaire cité, car un accès à l'index non contrôlé peut entraîner une erreur d'exécution. Si en JavaScript vous vous attendez à des choses comme ça, en TypeScript vous obtenez une confiance dangereuse et erronée. Les mauvais types sont pires que any .

eh bien maintenant que tu le dis comme ça je suis convaincu

Bien sûr, vous êtes l'un des principaux responsables de la maintenance et merci à vous, ainsi qu'à l'équipe et aux contributeurs pour ts. Mais il ne s'agit plus seulement de vous. Comparez les votes positifs : 365 avec les votes négatifs : 6 ! Seulement 6 ! Cela montre une énorme demande de sécurité de type.

Mais parlons de solutions. Y a-t-il une solution que l'équipe ferait se produire ou peut penser?

Pouvez-vous s'il vous plaît élaborer un peu plus sur ce qui ne va pas avec ts-ignore dans ce cas ? Je veux dire qu'il peut être automatisé avec des outils comme codemod et ne change pas le comportement du code actuel. Bien sûr, ce n'est pas une belle solution de contournement, mais bon, c'est une façon possible d'introduire ce changement sans drapeaux.

Que pensez-vous de la publication automatique d'une version patchée de ts, par exemple 4.0.0-breaking ? Il introduit un peu (beaucoup ?) de travail sur les conflits, mais il permet à chacun de tester les changements et de préparer la base de code (pas seulement pour cette demande de fonctionnalité). Cela pourrait être fait pour une période de temps limitée, comme 3-6 mois. Nous serions les premiers à utiliser cette version.

plus. Comparez les votes positifs : 365 avec les votes négatifs : 6 ! Seulement 6 ! Cela montre une énorme demande de sécurité de type.
- @Bessonov

ha-ha.
Non pas que je sois d'accord avec tout le post de @RyanCavanaugh .. mais... allez..
il y a quoi des millions ? Les utilisateurs de TS là-bas ?!? 365 veulent que cette fonctionnalité soit suffisante pour commenter et voter dans ce fil...

Il n'y a pas de notification de github, mais à tous ceux qui essaieraient de jouer avec un index non défini de ce problème, jetez un œil au projet de PR au-dessus de mon commentaire.

@RyanCavanaugh merci beaucoup de nous avoir donné l'opportunité de jouer avec. Je l'ai exécuté sur une base de code assez petite (~ 105 fichiers ts(x)) et j'ai joué un peu avec. Je n'ai trouvé aucun problème important. Une ligne qui a été modifiée de :

const refHtml = useRef(useMemo(() => document.getElementsByTagName('html')[0], []))

à:

const refHtml = useRef(useMemo(() => document.getElementsByTagName('html')[0] ?? null, []))

Je vais l'essayer sur un projet moyen la semaine prochaine.

@lonewarrior556 c'est 60 fois plus et c'est sur la première page si vous triez par votes positifs :)

@Bessonov Je pense que vous devriez l'essayer sur la base de code du compilateur Typescript, cela provoquera probablement une rupture massive en raison de l'utilisation non triviale des boucles for.

Vous pouvez même avoir des indicateurs pour activer/désactiver la compatibilité descendante.

Chaque indicateur que vous ajoutez est une autre matrice de configuration qui doit être testée et prise en charge. Pour cette raison, TypeScript souhaite réduire au minimum les indicateurs.

@MatthiasKunnen S'il s'agissait d'une petite fonctionnalité de cas, je serais d'accord pour dire que c'est une raison valable de ne pas ajouter de drapeau pour cela. Mais l'accès direct aux tableaux apparaît assez fréquemment dans le code et constitue à la fois un trou flagrant dans le système de type et également un changement décisif à corriger, je pense donc qu'un indicateur serait justifié.

Un drapeau n'est pas la bonne approche, en partie à cause de la combinatoire
problème. Nous devrions juste appeler ce TypeScript v5... ce genre d'approche
fait passer le nombre de combinaisons testables de 2^N à seulement N...

Le samedi 15 août 2020 à 15h56, Michael Burkman [email protected]
a écrit:

@MatthiasKunnen https://github.com/MatthiasKunnen Si c'était
petite caractéristique de cas de bord alors je conviendrais que c'est une raison valable de ne pas
ajouter un drapeau pour cela. Mais l'accès direct au tableau apparaît dans le code assez
fréquemment, et est à la fois un trou flagrant dans le système de type et aussi un
changement de rupture à corriger, donc je pense qu'un drapeau serait justifié.

-
Vous recevez ceci parce que vous êtes abonné à ce fil.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/microsoft/TypeScript/issues/13778#issuecomment-674408067 ,
ou se désinscrire
https://github.com/notifications/unsubscribe-auth/AAADY5AXEY65S5HDGNGIPZDSA2O3FANCNFSM4C6KEKAA
.

Une erreur indéfinie inattendue vient de faire tomber mon application pour des centaines d'utilisateurs, une erreur qui aurait été détectée au moment de la construction si cette vérification de type avait été en place. TypeScript a été un moyen incroyable pour nous de fournir un logiciel plus fiable, mais sa grande utilité est sapée par cette seule omission.

Que diriez-vous que les 10 prochaines personnes qui souhaitent commenter ici testent plutôt le projet de PR #39560 ?

C'est très ennuyeux si vous voulez activer la règle @typescript-eslint/no-unnecessary-condition ESLint car elle se plaint alors de toutes les instances de

if (some_array[i] === undefined) {

Il pense que c'est une condition inutile (parce que Typescript le dit !) mais ce n'est pas le cas. Je ne veux pas vraiment avoir à ajouter // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition partout dans ma base de code.

Si corriger cela correctement est trop difficile pour les programmeurs Javascript paresseux, nous pourrions peut-être avoir une syntaxe d'accès au tableau alternative qui ajouterait undefined , par exemple

if (some_array[?i] === undefined) {

(suggestion de syntaxe aléatoire ; alternatives bienvenues)

@Timmmm s'il vous plaît lire le commentaire juste au eslint .

@the0rted J'ai lu ce commentaire. Il ne met pas en œuvre une version de ma suggestion. Peut-être devriez-vous simplement le relire.

Désolé, quand j'ai dit "cette" suggestion, je voulais dire la capacité du PO, c'est-à-dire traiter array_of_T[i] comme T | undefined . Je vois que vous demandez une syntaxe pour marquer un opérateur d'index spécifique comme "peut-être indéfini", mais si vous utilisez l'implémentation dans le PR de Ryan, vous n'en auriez pas besoin, car tous les opérateurs d'index seraient "peut-être indéfinis" . Cela ne répondrait-il pas à votre besoin ?

Oui, ce serait le cas - si/quand il atterrira, je l'utiliserai certainement. Mais j'ai eu l'impression d'après cette discussion que beaucoup de gens y étaient résistants car cela ajoute un indicateur supplémentaire et rend le code qui semble devoir fonctionner plus compliqué (l'exemple de la boucle for simple).

Je voulais donc proposer une suggestion alternative, mais apparemment les gens la détestent. :-/

Toute contribution bien intentionnée est appréciée, ne le prenez pas mal @Timmmm. Je pense que les votes négatifs ne font que montrer que c'est déjà un terrain bien foulé dans ce fil à ce stade.

Pour moi, l'ajout d'un indicateur qui améliore la sécurité des types dans tous les domaines est beaucoup moins coûteux que l'introduction d'une nouvelle syntaxe d'opt-in.

Je ne l'ai pas mentionné ci-dessus, mais une syntaxe pour les remplacements ponctuels existe déjà : if ((some_array[i] as MyType | undefined) === undefined) . Ce n'est pas aussi laconique qu'un nouveau raccourci, mais j'espère que ce n'est pas une construction que vous auriez à utiliser très souvent.

_Initialement publié par @osyrisrblx dans https://github.com/microsoft/TypeScript/issues/40435#issuecomment -690017567_

Bien que cela semble être une option beaucoup plus sûre, il serait bien de désactiver ce comportement avec une syntaxe distincte pour les rares occasions où vous êtes sûr à 100% qu'il est toujours défini.

Peut-être que !: pourrait affirmer toujours défini ?

interface X {
    [index: string]!: number; // -> number
}

interface Y {
    [index: string]: number; // -> number | undefined
}

Cette suggestion dénature le problème. Le problème ne vient pas de la compréhension de TypeScript du type lui-même. Le problème vient de _l'accès_ aux données, où une nouvelle syntaxe a déjà été proposée plus tôt dans ce fil.

Vous pouvez l'essayer dans la version bêta de TypeScript 4.1, qui sortira bientôt ™ (ou ce soir tous les soirs si vous en avez besoin maintenant ! 🙂)

Désolé si cela a déjà été proposé, mais {[index: string]?: number} // -> number | undefined pourrait-il être pris en charge ? C'est cohérent avec la syntaxe des propriétés optionnelles de l'interface - requise par défaut, éventuellement non définie avec "?".

L'option du compilateur dans 4.1 est cool, mais avoir un contrôle plus granulaire serait bien aussi.

Si vous voulez l'essayer dans votre repo (il m'a fallu un peu de temps pour comprendre):

  1. Installer typescript@suivant yarn (add|upgrade) typescript@next
  2. Ajouter un indicateur (pour moi dans tsconfig.json) "noUncheckedIndexedAccess": true

En activant cette règle sur mon projet, je suis tombé sur cette erreur intéressante :

type MyRecord = { a: number; b: string };

declare const myRecord: MyRecord;

declare const key: 'a' | 'b';
const value = myRecord[key]; // string | number ✅

// ❌ Unexpected error
// Type 'MyRecord[Key] | undefined' is not assignable to type 'MyRecord[Key]'
const fn = <Key extends keyof MyRecord>(key: Key): MyRecord[Key] => myRecord[key];

Dans ce cas, je ne m'attendais pas à ce que myRecord[key] renvoie le type MyRecord[Key] | undefined , car le key est contraint à keyof MyRecord .

Mise à jour : problème classé https://github.com/microsoft/TypeScript/issues/40666

C'est probablement un bug/un oubli !

En activant cette règle sur mon projet, je suis tombé sur cette erreur intéressante :

type MyRecord = { a: number; b: string };

declare const myRecord: MyRecord;

declare const key: 'a' | 'b';
const value = myRecord[key]; // string | number ✅

// ❌ Unexpected error
// Type 'MyRecord[Key] | undefined' is not assignable to type 'MyRecord[Key]'
const fn = <Key extends keyof MyRecord>(key: Key): MyRecord[Key] => myRecord[key];

Dans ce cas, je ne m'attendais pas à ce que myRecord[key] renvoie le type MyRecord[Key] | undefined , car le key est contraint à keyof MyRecord .

Je dirais que c'est un bug. Fondamentalement, si keyof Type n'inclut que les types littéraux de chaîne/nombre (au lieu d'inclure en réalité string ou number ), alors Type[Key]Key extends keyof Type ne devrait pas inclure undefined , je pense.

réticulation vers #13195, qui examine également les différences et les similitudes entre "il n'y a pas de propriété ici" et "il y a une propriété ici mais c'est undefined "

Suivi de certains problèmes liés aux limitations de conception ici

  • #41612

Pour info : j'ai attrapé deux bugs dans ma base de code grâce à ce flag, alors un grand merci !
C'est un peu ennuyeux que la vérification de arr.length > 0 ne soit pas suffisante pour protéger arr[0] , mais c'est un inconvénient mineur (je peux réécrire la vérification pour rendre tsc heureux) par rapport à la sécurité supplémentaire, IMO.

@rubenlg Je suis d'accord sur arr.length . Pour juste vérifier le premier élément, j'ai réécrit notre code comme

const firstEl = arr[0];
if (firstEl !== undefined) {
  ...
}

Mais il y a quelques endroits où nous faisons if (arr.length > 2) ou plus, ce qui est un peu gênant. Cependant, je ne pense pas que la vérification de .length serait totalement sécurisée de toute façon puisque vous pouvez simplement la modifier :

const a: number[] = [];

a.length = 1;

if (a.length > 0) {
    const b: number = a[0];
    console.log(b);
}

Imprime undefined .

Cependant, je ne pense pas que la vérification de .length serait totalement sécurisée de toute façon puisque vous pouvez simplement la modifier :

const a: number[] = [];

a.length = 1;

if (a.length > 0) {
    const b: number = a[0];
    console.log(b);
}

Imprime undefined .

Ce serait essentiellement un tableau clairsemé, ce qui est hors de portée pour ce genre de chose. Il y a toujours des choses non standard ou sournoises qui pourraient être faites pour contourner le vérificateur de type.

Il y a toujours un moyen d'infirmer l'hypothèse précédente, et c'est le problème. Analyser tous les chemins d'exécution possibles serait beaucoup trop coûteux.

const a: number[] = [1]
if (a.length > 0) {
    a.pop();
    console.log(a[0])
}
Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

wmaurer picture wmaurer  ·  3Commentaires

blendsdk picture blendsdk  ·  3Commentaires

weswigham picture weswigham  ·  3Commentaires

DanielRosenwasser picture DanielRosenwasser  ·  3Commentaires

jbondc picture jbondc  ·  3Commentaires