Typescript: Vorschlag: Option zum Einschließen von undefined in Indexsignaturen

Erstellt am 31. Jan. 2017  ·  242Kommentare  ·  Quelle: microsoft/TypeScript

Update : für meinen neuesten Vorschlag siehe Kommentar https://github.com/Microsoft/TypeScript/issues/13778#issuecomment -406316164

Wenn strictNullChecks aktiviert ist, schließt TypeScript undefined in Indexsignaturen ein (zB auf einem Objekt oder Array). Dies ist ein bekannter Vorbehalt und wird in mehreren Ausgaben diskutiert, nämlich https://github.com/Microsoft/TypeScript/issues/9235 , https://github.com/Microsoft/TypeScript/issues/13161 , https://github .com/Microsoft/TypeScript/issues/12287 und https://github.com/Microsoft/TypeScript/pull/7140#issuecomment -192606629.

Beispiel:

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

Es scheint jedoch, dass viele TypeScript-Benutzer sich beim Lesen der oben genannten Probleme wünschen, dass dies nicht der Fall wäre. Zugegeben, wenn Indexsignaturen undefined , erfordert der Code wahrscheinlich viel mehr Schutz, aber – für einige – ist dies ein akzeptabler Kompromiss für erhöhte Typsicherheit.

Beispiel für Indexsignaturen einschließlich undefined :

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

Ich würde gerne wissen, ob dieses Verhalten als zusätzliche Compileroption zusätzlich zu strictNullChecks . Auf diese Weise können wir alle Benutzergruppen zufriedenstellen: diejenigen, die strenge Nullprüfungen mit oder ohne undefiniert in ihren Indexsignaturen wünschen.

Committed Suggestion

Hilfreichster Kommentar

Wir bleiben ziemlich skeptisch, ob jemand in der Praxis von dieser Flagge profitieren würde. Karten und kartenähnliche Dinge können sich bereits auf ihren Definitionsseiten für | undefined anmelden

Ist es nicht eines der Ziele von TypeScript, Fehler beim "Kompilieren" abzufangen, anstatt sich darauf zu verlassen, dass sich der Benutzer an etwas Bestimmtes erinnert / weiß, dass es etwas Bestimmtes tut? Dies scheint diesem Ziel zu widersprechen; dass der Benutzer etwas tun muss, um Abstürze zu vermeiden. Dasselbe könnte für viele andere Funktionen gesagt werden; Sie werden nicht benötigt, wenn der Entwickler immer x tut. Das Ziel von TypeScript ist (vermutlich), die Arbeit zu erleichtern und diese Dinge zu beseitigen.

Ich bin auf diesen Fehler gestoßen, weil ich strictNullChecks für vorhandenen Code aktiviert habe und bereits einen Vergleich hatte, sodass ich den Fehler erhielt. Wenn ich brandneuen Code geschrieben hätte, hätte ich das Problem hier wahrscheinlich nicht erkannt (das Typsystem sagte mir, dass ich immer einen Wert erhalte) und wäre mit einem Laufzeitfehler geendet. Sich darauf zu verlassen, dass sich TS-Entwickler daran erinnern (oder noch schlimmer, sogar wissen), dass sie alle ihre Maps mit | undefined deklarieren sollen, fühlt sich an, als würde TypeScript nicht das tun, wofür die Leute es eigentlich wollen.

Alle 242 Kommentare

Mit Ausnahme von strictNullChecks haben wir keine Flags, die das Typsystemverhalten ändern. Flags aktivieren/deaktivieren normalerweise die Fehlerberichterstattung.
Sie können immer eine benutzerdefinierte Version der Bibliothek haben, die alle Indexer mit | undefined . sollte wie erwartet funktionieren.

@mhegazy Das ist eine interessante Idee. Irgendeine Anleitung zum Überschreiben der Typsignaturen für Array/Objekt?

Es gibt interface Array<T> in lib.d.ts . Ich habe nach dem Regexp \[\w+: (string|number)\] gesucht, um auch andere Indizierungssignaturen zu finden.

Interessant, also habe ich folgendes probiert:

{
    // 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 :-(
}

Irgendwelche Ideen?

Kopieren Sie lib.d.ts lokal, sagen Sie lib.strict.d.ts , ändern Sie die Indexsignatur in [n: number]: T | undefined; , fügen Sie die Datei in Ihre Zusammenstellung ein. Sie sollten den beabsichtigten Effekt sehen.

Cool, danke dafür.

Das Problem mit dem hier vorgeschlagenen Fix besteht darin, dass eine separate lib Datei gespalten und verwaltet werden muss.

Ich frage mich, ob diese Funktion genug verlangt wird, um eine Art Option out of the box zu rechtfertigen.

Nebenbei bemerkt ist es interessant, dass die Typsignatur für die Methode get in ES6-Sammlungen ( Map / Set ) T | undefined zurückgibt, wenn Array / Object Indexsignaturen nicht.

das ist eine bewusste entscheidung. Es wäre sehr ärgerlich, wenn dieser Code ein Fehler wäre:

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

und es wäre unvernünftig, jeden Benutzer zu bitten, ! . oder schreiben

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

Bei Karten ist dies im Allgemeinen nicht der Fall.

Ähnlich können Sie für Ihre Typen | undefined für alle Ihre Indexsignaturen angeben, und Sie erhalten das erwartete Verhalten. aber für Array es nicht vernünftig. Sie können die Bibliothek gerne abzweigen und alle erforderlichen Änderungen vornehmen, aber wir planen derzeit nicht, die Deklaration in der Standardbibliothek zu ändern.

Ich glaube nicht, dass wir eine Flagge hinzufügen würden, um die Form einer Erklärung zu ändern.

@mhegazy, aber für Arrays mit Löchern ist a[i] möglicherweise nicht definiert:

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

Ausgabe ist:

undefined
undefined
undefined
undefined
0

Wir bleiben ziemlich skeptisch, ob jemand in der Praxis von dieser Flagge profitieren würde. Karten und kartenähnliche Dinge können sich bereits auf ihren Definitionsseiten für | undefined anmelden, und das Erzwingen eines EULA-ähnlichen Verhaltens beim Array-Zugriff scheint kein Gewinn zu sein. Wir müssten wahrscheinlich CFA und Typenwächter erheblich verbessern, um dies schmackhaft zu machen.

Wenn jemand seine lib.d.ts ändern und alle Downstream-Brüche in seinem eigenen Code beheben und zeigen möchte, wie der Gesamtunterschied aussieht, um zu zeigen, dass dies ein Wertversprechen hat, sind wir für diese Daten offen. Alternativ, wenn viele Leute wirklich begeistert sind, Postfix ! mehr zu verwenden, aber noch nicht genügend Möglichkeiten dazu haben, wäre dieses Flag eine Option.

Wir bleiben ziemlich skeptisch, ob jemand in der Praxis von dieser Flagge profitieren würde. Karten und kartenähnliche Dinge können sich bereits auf ihren Definitionsseiten für | undefined anmelden

Ist es nicht eines der Ziele von TypeScript, Fehler beim "Kompilieren" abzufangen, anstatt sich darauf zu verlassen, dass sich der Benutzer an etwas Bestimmtes erinnert / weiß, dass es etwas Bestimmtes tut? Dies scheint diesem Ziel zu widersprechen; dass der Benutzer etwas tun muss, um Abstürze zu vermeiden. Dasselbe könnte für viele andere Funktionen gesagt werden; Sie werden nicht benötigt, wenn der Entwickler immer x tut. Das Ziel von TypeScript ist (vermutlich), die Arbeit zu erleichtern und diese Dinge zu beseitigen.

Ich bin auf diesen Fehler gestoßen, weil ich strictNullChecks für vorhandenen Code aktiviert habe und bereits einen Vergleich hatte, sodass ich den Fehler erhielt. Wenn ich brandneuen Code geschrieben hätte, hätte ich das Problem hier wahrscheinlich nicht erkannt (das Typsystem sagte mir, dass ich immer einen Wert erhalte) und wäre mit einem Laufzeitfehler geendet. Sich darauf zu verlassen, dass sich TS-Entwickler daran erinnern (oder noch schlimmer, sogar wissen), dass sie alle ihre Maps mit | undefined deklarieren sollen, fühlt sich an, als würde TypeScript nicht das tun, wofür die Leute es eigentlich wollen.

Wir bleiben ziemlich skeptisch, ob jemand in der Praxis von dieser Flagge profitieren würde. Karten und kartenähnliche Dinge können sich bereits für | . anmelden undefined an ihren Definitionsorten
Ist es nicht eines der Ziele von TypeScript, Fehler beim "Kompilieren" abzufangen, anstatt sich darauf zu verlassen, dass sich der Benutzer an etwas Bestimmtes erinnert / weiß, dass es etwas Bestimmtes tut?

Eigentlich ist das Ziel:

1) Identifizieren Sie statisch Konstrukte, bei denen es sich wahrscheinlich um Fehler handelt.

Diskutiert wird hier die Fehlerwahrscheinlichkeit (nach Meinung des TypeScript-Teams gering) und die gemeinsame produktive Nutzbarkeit der Sprache. Einige der frühen Änderungen an CFA bestanden darin, weniger alarmierend zu sein oder die CFA-Analyse zu verbessern, um diese Dinge intelligenter zu bestimmen.

Ich denke, die Frage des TypeScript-Teams lautet, anstatt die strikte Korrektheit zu argumentieren, um Beispiele dafür zu geben, wo diese Art von Strenge im allgemeinen Gebrauch tatsächlich einen Fehler identifizieren würde, gegen den man sich schützen sollte.

Ich bin in diesem Kommentar ein wenig mehr auf die Argumentation eingegangen https://github.com/Microsoft/TypeScript/issues/11238#issuecomment -250562397

Denken Sie an die beiden Arten von Schlüsseln in der Welt: Diejenigen, von denen Sie wissen , dass sie eine entsprechende Eigenschaft in einem Objekt haben (sicher), diejenigen, von denen Sie nicht wissen, dass sie eine entsprechende Eigenschaft in einem Objekt haben (gefährlich).

Sie erhalten die erste Art von Schlüssel, einen "sicheren" Schlüssel, indem Sie den richtigen Code schreiben wie

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

oder

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

Sie erhalten die zweite Art von Schlüssel, die "gefährliche" Art, von Dingen wie Benutzereingaben oder zufälligen JSON-Dateien von der Festplatte oder einer Liste von Schlüsseln, die möglicherweise vorhanden, aber nicht vorhanden sind.

Wenn Sie also einen gefährlichen Schlüssel haben und danach indexieren, wäre es schön, hier | undefined zu haben. Aber der Vorschlag lautet nicht " Gefährliche Schlüssel als gefährlich behandeln", sondern "Behandeln Sie alle Schlüssel, auch sichere, als gefährlich". Und sobald Sie anfangen, sichere Schlüssel als gefährlich zu behandeln, ist das Leben wirklich scheiße. Du schreibst Code wie

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

und TypeScript beschwert sich über Sie, dass arr[i] undefined auch wenn ich es gerade @#%#getestet habe . Jetzt haben Sie sich angewöhnt, Code wie diesen zu schreiben, und es fühlt sich dumm an:

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

Oder vielleicht schreiben Sie Code wie diesen:

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

und TypeScript sagt "Hey, dieser Indexausdruck könnte | undefined , also beheben Sie ihn pflichtbewusst, weil Sie diesen Fehler bereits 800 Mal gesehen haben:

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

Aber du hast den Fehler nicht behoben . Sie wollten Object.keys(yourObj) oder vielleicht myObj[k] schreiben. Das ist der schlimmste Compilerfehler, weil er Ihnen in keinem Szenario wirklich hilft - es wird nur das gleiche Ritual auf jede Art von Ausdruck angewendet, ohne Rücksicht darauf, ob es tatsächlich gefährlicher war als jeder andere Ausdruck der gleichen Form.

Ich denke an das alte "Möchten Sie diese Datei wirklich löschen?" Dialog. Wenn dieser Dialog jedes Mal erscheinen würde, wenn Sie versuchten, eine Datei zu löschen, würden Sie sehr schnell lernen, del y wenn Sie früher del gedrückt haben, und Ihre Chancen, etwas Wichtiges nicht zu löschen, werden auf den vorherigen Wert zurückgesetzt -Dialog-Grundlinie. Wenn der Dialog stattdessen nur beim Löschen von Dateien angezeigt wurde, die nicht in den Papierkorb wanderten, haben Sie jetzt eine sinnvolle Sicherheit. Aber wir haben keine Ahnung (und könnten es auch nicht), ob Ihre Objektschlüssel sicher sind oder nicht. Dialog jedes Mal, wenn Sie dies tun, werden Fehler wahrscheinlich nicht besser gefunden, als wenn nicht alle angezeigt werden.

Identifizieren Sie statisch Konstrukte, bei denen es sich wahrscheinlich um Fehler handelt.

Vielleicht muss dies dahingehend geändert werden, dass "Konstrukte statisch identifizieren, bei denen es sich wahrscheinlicher als bei anderen um Fehler handelt". : zwinker:. Ich werde daran erinnert, wenn wir Fehler bekommen, die im Wesentlichen sind: "Ich habe * als ich / . Können Sie mit make * eine Warnung verwenden?"

Ich verstehe, aber ein Index außerhalb des zulässigen Bereichs ist ein echtes und häufiges Problem. Leute dazu zu zwingen, Arrays so aufzuzählen, dass sie dies nicht können, wäre keine schlechte Sache.

Der Fix mit ! gefällt mir eigentlich auch nicht - was ist, wenn jemand vorbeikommt und eine Änderung vornimmt, so dass die Annahme nun ungültig ist? Sie sind wieder bei Punkt eins (ein potenzieller Laufzeitfehler für etwas, das der Compiler abfangen sollte). Es sollte sichere Möglichkeiten zum Aufzählen von Arrays geben, die sich nicht darauf verlassen, dass die Typen gelogen oder ! (zB können Sie nicht etwas wie array.forEach(i => console.log(i.name) tun?).

Sie haben bereits Typen basierend auf Code eingegrenzt, könnten Sie also theoretisch nicht Muster erkennen, die sicher sind, den Typ einzugrenzen, um in diesen Fällen | undefined zu entfernen und das Beste aus beiden Welten zu erhalten? Ich würde argumentieren, dass, wenn Sie dem Compiler nicht leicht mitteilen können, dass Sie nicht auf ein gültiges Element zugreifen, Ihre Garantie möglicherweise entweder ungültig ist oder in Zukunft leicht versehentlich gebrochen werden könnte.

Allerdings verwende ich TS nur für ein Projekt und das wird letztendlich zu Dart migriert, daher ist es unwahrscheinlich, dass es für mich einen wirklichen Unterschied macht. Ich bin nur traurig, dass die allgemeine Qualität der Software schlecht ist und es hier eine Möglichkeit gibt, Fehler zu beseitigen, die scheinbar aus Bequemlichkeitsgründen ignoriert werden. Ich bin sicher, das Typensystem könnte solide gemacht und die üblichen Ärgernisse auf eine Weise angegangen werden, die diese Löcher nicht einführt.

Wie auch immer, das sind nur meine 2 Cent.. Ich möchte das nicht in die Länge ziehen - Sie verstehen sicher, woher wir kommen, und Sie sind viel besser in der Lage, Entscheidungen darüber zu treffen als ich :-)

Ich denke, es gibt ein paar Dinge zu beachten. Es gibt viele Muster zum Durchlaufen von Arrays, die häufig verwendet werden und die die Anzahl der Elemente berücksichtigen. Während es ein mögliches Muster ist, nur zufällig auf Indizes auf Arrays zuzugreifen, ist dies in der Wildnis ein sehr ungewöhnliches Muster und wahrscheinlich kein statischer Fehler. Es gibt zwar moderne Möglichkeiten zum Iterieren, die gebräuchlichste wäre jedoch etwa:

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

Wenn Sie davon ausgehen, dass Ersatz-Arrays ungewöhnlich sind (das sind sie), ist es wenig hilfreich, value | undefined . Wenn es in der Wildnis ein gemeinsames Muster gibt, bei dem dies riskant (und wahrscheinlich ein Fehler) ist, dann würde das TypeScript meiner Meinung nach darauf hören, dies zu berücksichtigen, aber die Muster, die allgemein verwendet werden, müssen erneut gegen alle Werte von an Indexzugriff undefiniert ist eindeutig etwas, das sich auf die Produktivität auswirkt und, wie bereits erwähnt, aktiviert werden kann, wenn Sie sich in einer Situation befinden, in der es möglicherweise nützlich ist.

Ich denke, es gab bereits Diskussionen über die Verbesserung des CFA, damit es eine Möglichkeit gibt, die Co-Abhängigkeit von Werten auszudrücken (zB Array.prototype.length bezieht sich auf den Indexwert), sodass Dinge wie der Index außerhalb der Grenzen statisch analysiert werden können. Offensichtlich ist das ein bedeutendes Stück Arbeit, das mit allen möglichen Randfällen und Überlegungen angestellt ist, die ich nicht ergründen möchte (obwohl Anders wahrscheinlich bei solchen Dingen in kaltem Schweiß aufwacht).

Es wird also zu einem Kompromiss ... Ohne CFA-Verbesserungen 90 % des Codes mit roten Heringen komplizieren, um potenziell 10 % schlechten Code zu erkennen . Andernfalls investiert es in große CFA-Verbesserungen, die mit ihren eigenen Konsequenzen für die Stabilität und Problemen gegen das Finden von unsicherem Code verbunden sein könnten.

Es gibt nur so viel, was TypeScript tun kann, um uns vor uns selbst zu retten.

All dieser Fokus liegt auf Arrays und ich stimme zu, dass es dort weniger wahrscheinlich ein Problem ist, aber die meisten der ursprünglich angesprochenen Probleme (wie meine) betrafen Karten, bei denen meiner Meinung nach nicht der übliche Fall immer vorhandene Schlüssel sind?

All dieser Fokus liegt auf Arrays und ich stimme zu, dass es dort weniger wahrscheinlich ein Problem ist, aber die meisten der ursprünglich angesprochenen Probleme (wie meine) betrafen Karten, bei denen meiner Meinung nach nicht der übliche Fall immer vorhandene Schlüssel sind?

Wenn dies Ihr Typ ist, fügen Sie | undefined zur Indexsignatur hinzu. Es ist bereits ein Fehler, in einen Typ ohne Indexsignatur unter --noImplicitAny zu indizieren.
ES6 Map ist bereits mit get als get(key: K): V | undefined; .

Ich habe alle Definitionen von Arrays und Maps umgeschrieben, um Indexsignaturen zu machen, die | undefined , habe es seitdem nie bereut, habe ein paar Fehler gefunden, es verursacht keine Beschwerden, da ich mit Arrays indirekt über eine handgemachte Lib arbeite, die Überprüfungen hält für undefined oder ! darin

Es wäre großartig, wenn TypeScript den Fluss der Prüfungen wie C# steuern könnte (um Indexbereichsprüfungen zu eliminieren, um etwas Prozessorzeit zu sparen), zum Beispiel:

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
}

(für diejenigen, die spärliche Arrays verwenden - töten Sie sich mit heißem brennendem Feuer)

Was Object.keys , braucht es einen speziellen Typ, sagen wir allkeysof T , damit die Kontrollflussanalyse sichere Eingrenzungen vornehmen kann

Ich denke, dies wäre eine gute Option, denn im Moment lügen wir im Wesentlichen über die Art der Indizierungsoperation, und es kann leicht sein, dass ich vergessen kann, meinen Objekttypen | undefined hinzuzufügen. Ich denke, das Hinzufügen von ! in den Fällen, in denen wir wissen, dass wir undefined s ignorieren möchten, wäre eine gute Möglichkeit, mit Indizierungsvorgängen umzugehen, wenn diese Option aktiviert ist.

Es gibt (mindestens) zwei weitere Probleme beim Einfügen von |undefined in Ihre Objekttypdefinitionen:

  • es bedeutet, dass Sie diesen Objekten undefined zuordnen können, was definitiv nicht beabsichtigt ist
  • andere Operationen wie Object.values (oder _.values ) erfordern, dass Sie undefined in den Ergebnissen verarbeiten

tslint meldet eine falsch-positive Warnung vor einer konstanten Bedingung, weil Typskript falsche Typinformationen zurückgibt (= | undefined fehlt).
https://github.com/palantir/tslint/issues/2944

Einer der regelmäßig übersprungenen Fehler mit dem Fehlen von | undefined in der Array-Indizierung ist dieses Muster, wenn es anstelle von 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.

Ich würde diese Flagge auf jeden Fall verwenden. Ja, die Arbeit mit alten for Schleifen wird nervig sein, aber wir haben den ! Operator, der dem Compiler mitteilt, wenn wir wissen, dass er definiert ist:

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

Außerdem ist dieses Problem bei den neueren, viel besseren for of Schleifen kein Problem und es gibt sogar eine TSLint-Regel prefer-for-of , die Ihnen sagt, dass Sie keine for Schleifen im alten Stil verwenden sollen nicht mehr.

Derzeit habe ich das Gefühl, dass das Typensystem für den Entwickler inkonsistent ist. array.pop() erfordert eine if Prüfung oder eine ! Assertion, der Zugriff über [array.length - 1] jedoch nicht. ES6 map.get() erfordert eine if Prüfung oder eine ! Assertion, ein Objekt-Hash jedoch nicht. Das Beispiel von

Ein weiteres Beispiel ist die Destrukturierung:

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

Ich hätte es vorgezogen, wenn der Compiler mich dazu gezwungen hätte:

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')

Dies sind tatsächliche Fehler, die ich gesehen habe und die vom Compiler hätten verhindert werden können.

Imo sollte dies nicht in die Typisierungsdateien gehören, sondern sollte eher ein Typsystem-Mechaniker sein - wenn auf etwas mit einer Indexsignatur zugegriffen wird, kann es undefined . Wenn Ihre Logik sichergestellt hat, dass dies nicht der Fall ist, verwenden Sie einfach ! . Andernfalls fügen Sie if und Sie sind gut.

Ich denke, viele Leute würden es vorziehen, dass der Compiler mit einigen erforderlichen Assertionen streng ist, als mit nicht erkannten Fehlern locker zu sein.

Ich würde wirklich gerne sehen, dass diese Flagge hinzugefügt wird. In der Codebasis meiner Firma ist Array-Random Access die seltene Ausnahme und for Schleifen sind Code-Geruchs, die wir normalerweise mit Funktionen höherer Ordnung umschreiben möchten.

@pelotom was beschäftigt dich dann (da es so aussieht, als hättest du dich meistens aus Schwierigkeiten herausgeholt)?

@aleksey-bykov hauptsächlich Objektindexsignaturen, die häufig in Bibliotheken von Drittanbietern vorkommen. Ich möchte auf eine Eigenschaft auf { [k: string]: A } zugreifen, um mich zu warnen, dass das Ergebnis möglicherweise nicht definiert ist. Ich habe die Array-Indizierung nur erwähnt, weil sie als Argument dafür angeführt wurde, warum das Flag zu nervig wäre, um damit zu arbeiten.

Sie wissen, dass Sie sie genau so umschreiben können, wie Sie es möchten? (mit etwas Mehrarbeit)

Ja, ich könnte die Eingaben aller für sie umschreiben oder ich könnte ein Compiler-Flag einschalten 😜

Spielen Sie weiter Captain O...: Sie können Ihre lib.d.ts heute umschreiben und ein glücklicher Besitzer von mehr Soundcodebasis sein oder Sie können für die nächsten N Jahre auf die Flagge warten

@aleksey-bykov wie kann dies durch Umschreiben von lib.d.ts geschehen?

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

dann in der Array Definition in lib.es2015.core.d.ts ersetzen

[n: number]: T;

mit

[n: number]: T | undefined;

@aleksey-bykov vielleicht hast du den Teil verpasst, in dem ich sagte, dass ich mich nicht für Arrays interessiere. Mir ist wichtig, wo Bibliotheken von Drittanbietern etwas vom Typ { [k: string]: T } deklariert haben, und ich möchte, dass der Zugriff auf ein solches Objekt etwas möglicherweise Undefiniertes zurückgibt. Es gibt keine Möglichkeit, dies zu erreichen, indem Sie einfach lib.d.ts bearbeiten; es erfordert eine Änderung der Signaturen der betreffenden Bibliothek.

Haben Sie die Kontrolle über Definitionsdateien von Drittanbietern? wenn ja kannst du sie reparieren

Und jetzt sind wir wieder da

Ja, ich könnte die Eingaben aller für sie umschreiben oder ich könnte ein Compiler-Flag einschalten 😜

Die Zeit ist ein flacher Kreis.

Seien Sie nicht albern, Sie verwenden nicht "jedermanns Eingabe" oder? Es ist buchstäblich ein Arbeitstag maximal für ein typisches Projekt, dort gewesen, es getan

Ja, ich muss ständig die Eingaben anderer bearbeiten und möchte es weniger tun.

und du wirst es vielleicht in N Jahren tun, denn jetzt kannst du leiden oder mann machen

Danke für deinen unglaublich konstruktiven Input 👍

konstruktiver Input dazu ist wie folgt, dieses Thema muss geschlossen werden, denn:
A. entweder die Entscheidung, ob [x] undefined oder nicht, wird den Entwicklern überlassen

  • lassen Sie sie alles in ihren Köpfen behalten, wie sie es immer zuvor getan haben
  • oder indem Sie lib.d.ts und 3rd-party.d.ts wie vorgeschlagen ändern

B. oder es dauert eine spezielle Syntax / Typen / Flussanalyse / N Jahre, um etwas zu mildern, das in #a . leicht von Hand gemacht werden kann

Das Problem ist ein Vorschlag für (b), außer dass keine neue Syntax vorgeschlagen wird, es ist nur ein Compiler-Flag.

Es kommt darauf an, dass der Typ { [x: string]: {} } fast immer eine Lüge ist; abgesehen von der Verwendung von Proxy gibt es kein Objekt, das unendlich viele Eigenschaften haben kann, geschweige denn jede mögliche Zeichenkette. Der Vorschlag besteht darin, ein Compiler-Flag zu haben, das dies erkennt. Es kann sein, dass es für das Erreichte zu schwierig ist, dies umzusetzen; Ich überlasse diesen Anruf den Implementierern.

der Punkt ist, dass weder

  • T | undefined
  • noch T

ist richtig für den allgemeinen Fall

um es für den allgemeinen Fall richtig zu machen, müssen Sie die Informationen über die Vorgabe von Werten in die Typen ihrer Container codieren, was ein abhängiges Typsystem erfordert ... was an sich keine schlechte Sache ist :) aber könnte so komplex sein wie alle aktuellen Typoskript-Schreibsysteme, die bis heute gemacht wurden, um ... Ihnen einige Bearbeitungen zu ersparen?

T | undefined ist für den allgemeinen Fall richtig, aus Gründen, die ich gerade genannt habe. Ich ignoriere dein unsinniges Geschwafel über abhängige Typen, wünsche dir einen schönen Tag.

Du kannst mich so oft ignorieren wie du willst, aber T | undefined ist ein Overshoot für

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

Ich würde lieber T | undefined dort haben und dem Compiler mitteilen, dass index ein numerischer Indexbereich von items daher kommt es nicht heraus, wenn Grenzen angewendet werden, wenn es auf items angewendet wird for / while Schleifenformen könnte der Compiler dies automatisch ableiten; in komplexen Fällen kann es leider undefined s geben. Und ja, wertbasierte Typen würden hier gut passen; Literale String-Typen sind so nützlich, warum nicht literal boolean und number- und range/set-of-ranges-Typen haben? Was TypeScript angeht, versucht es alles abzudecken, was mit JavaScript ausgedrückt werden kann (im Gegensatz zu beispielsweise Elm, das dies einschränkt).

Es ist buchstäblich ein Arbeitstag maximal für ein typisches Projekt, dort gewesen, es getan

@aleksey-bykov, neugierig, was war deine Erfahrung nach dieser Änderung? Wie oft müssen Sie ! ? und wie oft meldet der Compiler tatsächliche Fehler?

@mhegazy ehrlich gesagt habe ich keinen großen Unterschied bemerkt, als ich von T zu T | undefined , und ich habe auch keine Fehler gefunden. Ich denke, mein Problem ist, dass ich mit Arrays über Dienstprogrammfunktionen arbeite, die ! in ihnen, also gab es buchstäblich keinen Effekt für den äußeren Code:

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

In welcher lib Datei finde ich die Indextypdefinition für Objekte? Ich habe Array von [n: number]: T auf [n: number]: T | undefined aktualisiert. Nun möchte ich das gleiche für Objekte machen.

Es gibt keine Standardschnittstelle (wie Array für Arrays) für Objekte mit der Indexsignatur, Sie müssen in Ihrem Code nach genauen Definitionen für jeden Fall suchen und diese korrigieren

Sie müssen in Ihrem Code nach genauen Definitionen für jeden Fall suchen und diese korrigieren

Wie wäre es mit einer direkten Schlüsselsuche? Z.B

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

Gibt es hier eine Möglichkeit, T | undefined anstelle von T durchzusetzen? Derzeit verwende ich diese Helfer überall in meiner Codebasis als typsichere Alternativen für Indexsuchen auf Arrays und Objekten:

// 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 Während ich dies schreibe, https://unsplash.com , der mit strengeren Indexsignaturtypen hätte abgefangen werden können.

Ich verstehe, betrachten Sie den zugeordneten Typoperator:

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

Wenn ein Flag wie --strictArrayIndex keine Option ist, da Flags nicht dazu gedacht sind, die lib.d.ts Dateien zu ändern. Vielleicht könnt ihr strenge Versionen von lib.d.ts Dateien wie " lib": ['strict-es6'] ?

Es könnte mehrere Verbesserungen enthalten, nicht nur einen strikten Array-Index. Beispiel: Object.keys :

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

Könnte sein:

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

Update vom heutigen SBS: Wir haben uns 30 Minuten lang angeschrien und nichts ist passiert. ️

@RyanCavanaugh Was ist ein SBS aus Neugier?

@radix "Vorschlags-Backlog-Slog"

Das ist interessant, denn die Lösung liegt auf der Hand:
sowohl T als auch T | undefined sind falsch, der einzig richtige Weg besteht darin, die Indexvariable auf die Kapazität ihres Containers aufmerksam zu machen, entweder durch Auswahl aus einer Menge oder durch Einschließen in einen bekannten numerischen Bereich

@RyanCavanaugh Ich habe darüber nachgedacht, es sieht so aus, als würde der folgende Trick 87% aller Fälle abdecken:

  • values[index] ergibt T wenn der Index in for (HERE; ...) deklariert ist
  • values[somethingElse] ergibt T | undefined für alle Variablen, die außerhalb von for declared deklariert sind

@aleksey-bykov haben wir etwas noch schlauer besprochen - dass es einen tatsächlichen Typschutzmechanismus für " arr[index] wurde von index < arr.length getestet geben könnte. Aber das würde nicht helfen, wenn Sie pop in der Mitte eine Schleife machen oder Ihr Array an eine Funktion übergeben, die Elemente daraus entfernt hat. Es scheint wirklich nicht so zu sein, dass die Leute nach einem Mechanismus suchen, der OOB-Fehler in 82,9% der Fälle verhindert - immerhin , ist es bereits so, dass ungefähr dieser Bruchteil des Array-Indizierungscodes sowieso korrekt ist.Zeremonie zum korrekten Code hinzuzufügen, ohne Fehler in den falschen Fällen zu finden, ist ein schlechtes Ergebnis.

um nicht zu implizieren, dass Aleksey jemals ein Array mutieren würde

Ich habe vor kurzem eine App von Elm auf Typescript portiert und Indexierungsoperationen, die falsch eingegeben wurden, sind wahrscheinlich eine der größten Fehlerquellen, auf die ich gestoßen bin, mit den strengsten Einstellungen von TS aktiviert (auch Dinge wie this sind ungebunden ).

  • Sie können Mutationen zum Container nicht verfolgen
  • Sie können Indexmanipulationen nicht verfolgen

mit diesem besagten wenig können Sie garantieren, wenn ja, warum sollten Sie es überhaupt versuchen, wenn ein typischer Anwendungsfall eine dumme Iteration durch alle Elemente innerhalb von < array.length ist?

wie gesagt normalerweise

  • Wenn ein Fehler auftritt, hat er wahrscheinlich seinen Ursprung in den Klauseln der for Anweisung: Initialisierung, Inkrementierung, Stoppbedingung und kann daher nicht überprüft werden, da eine vollständige Typprüfung erforderlich ist
  • Außerhalb von for Klauseln gibt es normalerweise keinen Platz für Fehler

Solange also der Index irgendwie deklariert ist (nichts, was wir mit Sicherheit sagen können), ist es einigermaßen fair zu glauben, dass der Index innerhalb des Schleifenkörpers korrekt ist

Aber das würde nicht helfen, wenn Sie in der Mitte eine Schleife platzieren oder Ihr Array an eine Funktion übergeben, die Elemente daraus entfernt hat

Das scheint ein wirklich schwaches Argument dagegen zu sein. Diese Fälle sind bereits kaputt - sie als Ausrede zu verwenden, um andere Fälle nicht zu reparieren, macht keinen Sinn. Niemand hier verlangt, dass einer dieser Fälle richtig gehandhabt wird oder dass er zu 100% korrekt ist. Wir wollen nur genaue Typen in einem wirklich gewöhnlichen Fall. Es gibt viele Fälle in TypeScript, die nicht perfekt gehandhabt werden; Wenn Sie etwas Seltsames tun, können die Typen falsch sein. In diesem Fall machen wir nichts Seltsames und die Typen sind falsch.

Zeremonie zum korrekten Code hinzufügen

Ich bin neugierig, ein Codebeispiel zu sehen, bei dem dies "Zeremonie zum richtigen Code hinzufügen" ist. Wie ich es verstehe, ist eine grundlegende for-Schleife über ein Array leicht zu erkennen/einzuschränken. Was ist der reale Code , der keine einfache for-Schleife ist, wo dies unbequem wird und kein potenzieller Fehler ist? (entweder jetzt oder könnte von einer trivialen Änderung in der Zukunft stammen). Ich sage nicht, dass es keine gibt; Ich kann es mir einfach nicht vorstellen und habe keine Beispiele gesehen (ich habe viele Beispiele mit for-Schleifen gesehen, aber wenn Sie nicht sagen, dass diese nicht eingegrenzt werden können, scheinen sie irrelevant zu sein).

ohne Käfer zu fangen

Es gab Beispiele sowohl für funktionierenden Code, der aus diesem Grund nicht kompiliert werden konnte, als auch für Code, der zur Laufzeit ausgelöst wird, weil die Typen den Entwickler in die Irre führen. Es ist Unsinn zu sagen, dass es keinen Wert hat, dies zu unterstützen.

Warum können wir es nicht einfach halten und überhaupt kein magisches Verhalten um alte for-Schleifen herum hinzufügen? Sie können immer ! , damit die Dinge funktionieren. Wenn Ihre Codebasis voller for Schleifen im alten Stil ist, verwenden Sie das Flag nicht. Alle modernen Codebasen, mit denen ich gearbeitet habe, verwenden entweder forEach oder for of , um Arrays zu iterieren, und diese Codebasen würden von der zusätzlichen Typsicherheit profitieren.

wir machen nichts Seltsames und die Typen sind falsch.

Dieser Absatz liest sich für mich wie ein guter Grund, diese Funktion nicht zu haben. Der Zugriff auf ein Array außerhalb der Grenzen ist eine seltsame Sache; die Typen sind nur "falsch", wenn Sie etwas Ungewöhnliches tun (OOBing). Die überwiegende Mehrheit des Codes, der aus einem Array gelesen wird, geschieht innerhalb von Grenzen; es wäre "falsch", undefined in diesen Fällen durch dieses Argument einzubeziehen.

... funktionierender Code, der nicht kompiliert werden kann

Mir sind keine bekannt - können Sie darauf hinweisen?

Warum können wir es nicht einfach halten und überhaupt kein magisches Verhalten um alte for-Schleifen herum hinzufügen? Sie können immer verwenden! damit die Dinge funktionieren.

Was ist der nützliche Unterschied zwischen dieser und einer TSLint-Regel, die besagt "Alle Array-Zugriffsausdrücke müssen ein nachgestelltes ! "?

@RyanCavanaugh meine Vermutung ist , wäre , dass die TSLint Regel nicht in der Lage sein zu verengen Arten oder Steuer- , Regel--Flow-Analyse (zB Einwickeln der Zugang in einem if , wirft eine Ausnahme, return ing oder continue ing, wenn es nicht gesetzt ist usw.). Es geht auch nicht nur um Array-Zugriffsausdrücke, sondern auch um Destrukturierung. Wie würde die Implementierung für das Beispiel in https://github.com/Microsoft/TypeScript/issues/13778#issuecomment -336265143 aussehen? Um ! zu erzwingen, müsste es im Wesentlichen eine Tabelle erstellen, um die Typen dieser Variablen als möglicherweise undefined zu verfolgen. Was für mich nach etwas klingt, das der Typprüfer tun sollte, nicht nach einem Linter.

@RyanCavanaugh

Dieser Absatz liest sich für mich wie ein guter Grund, diese Funktion nicht zu haben. Der Zugriff auf ein Array außerhalb der Grenzen ist eine seltsame Sache; die Typen sind nur "falsch", wenn Sie etwas Ungewöhnliches tun (OOBing).

... funktionierender Code, der nicht kompiliert werden kann

Mir sind keine bekannt - können Sie darauf hinweisen?

In meinem Fall war es kein Array, aber es wurde als Dupe geschlossen, daher gehe ich davon aus, dass dieses Problem auch diese abdecken soll. Siehe #11186 für mein ursprüngliches Problem. Ich habe eine Datei in eine Map geparst und sie dann mit undefined . IIRC Ich habe beim Vergleich den Fehler bekommen, dass sie nicht undefiniert sein können, obwohl sie es könnten (und waren).

Es ist immer erlaubt, etwas mit undefined zu vergleichen

was schade ist

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

Es ist immer erlaubt, etwas mit undefined zu vergleichen

Es ist so lange her, ich fürchte, ich erinnere mich nicht mehr genau, was der Fehler war. Ich hatte definitiv einen Fehler für Code, der einwandfrei funktionierte (ohne strictNullChecks ) und es führte mich zu den Tests, die zu dem obigen Fall führten.

Wenn ich etwas Zeit habe, werde ich sehen, ob ich es noch einmal genau herausbekomme, was es war. Es hatte definitiv mit dieser Typisierung zu tun.

Der größte Teil dieser Diskussion konzentrierte sich auf Arrays, aber meiner Erfahrung nach wäre diese Funktion am hilfreichsten beim Objektzugriff. Zum Beispiel sieht dieses (vereinfachte) Beispiel aus meiner Codebasis vernünftig aus und lässt sich gut kompilieren, ist aber definitiv ein Fehler:

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
}

Was Objekte betrifft und einfach die Signatur so ändert, dass sie | undefined , macht @types/node dies für process.env :

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

aber es erlaubt überhaupt keine Eingrenzung des Typs:

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

gibt

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

@felixfbecker

Ich glaube, das hängt mit diesem Fehler zusammen: https://github.com/Microsoft/TypeScript/issues/17960

Sie können dies umgehen, indem Sie die env var einer Variablen zuweisen und diese dann schützen:

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

Das Hinzufügen von | undefined in einigen Knotentypisierungen wurde vorgenommen, um eine Erweiterung dieser Schnittstelle mit häufig verwendeten Eigenschaften zu ermöglichen, um die Codevervollständigung zu erreichen. ea

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

Ohne das | undefined in der Indexsignatur beschwert sich TSC mit Property 'foo' of type 'string | undefined' is not assignable to string index type 'string'. .

Ich weiß, dass dies mit Kosten verbunden ist, wenn Sie mit Typen arbeiten müssen, die die zusätzlichen | undefined und solche, die es nicht haben.

Wäre echt schön, wenn wir das loswerden könnten.

Ich versuche, meine Gedanken zu diesem Thema zu sammeln.

Objekte sind in JavaScript eine bunte Mischung. Sie können für zwei Zwecke verwendet werden :

  • Wörterbücher, auch Karten genannt, deren Schlüssel unbekannt sind
  • Aufzeichnungen, bei denen die Schlüssel bekannt sind

Für Objekte, die als Datensätze verwendet werden, müssen keine Indexsignaturen verwendet werden. Stattdessen werden bekannte Schlüssel auf dem Schnittstellentyp definiert. Gültige Schlüsselsuchvorgänge geben den entsprechenden Typ zurück, ungültige Schlüsselsuchvorgänge greifen auf die Indexsignatur zurück. Wenn keine Index-Signatur definiert ist (was für Objekte, die als Datensätze verwendet werden, nicht vorhanden sein sollte) und noImplicitAny aktiviert ist, wird dies wie gewünscht fehleranfällig.

Für Objekte, die als Wörterbücher (auch bekannt als Maps) und Arrays verwendet werden, werden Indexsignaturen verwendet, und wir können | undefined in den Werttyp aufnehmen. Beispiel: { [key: index]: string | undefined } . Alle Schlüsselsuchvorgänge sind gültig (da Schlüssel zur Kompilierzeit nicht bekannt sind) und alle Schlüssel geben denselben Typ zurück (in diesem Beispiel T | undefined ).

Da Indexsignaturen nur für das Dictionary-Objektmuster und die Arrays verwendet werden sollten, ist es wünschenswert, dass TypeScript | undefined im Indexsignaturwerttyp erzwingt: Wenn die Schlüssel nicht bekannt sind und Schlüsselsuchvorgänge möglicherweise undefined .

Es gibt gute Beispiele für Fehler, die ohne dies unsichtbar erscheinen könnten, wie Array.prototype.find die undefined , oder Schlüsselsuchvorgänge wie array[0] die undefined . (Destrukturierung ist nur Zuckersyntax für Schlüsselsuchvorgänge.) Es ist möglich, Funktionen wie getKey zu schreiben, um den Rückgabetyp zu korrigieren, aber wir müssen uns auf Disziplin verlassen, um die Verwendung dieser Funktionen in einer Codebasis durchzusetzen.

Wenn ich das richtig verstehe, geht es dann um die Reflexion über Wörterbuchobjekte und -arrays, so dass beim Zuordnen der Schlüssel eines Wörterbuchobjekts oder -arrays Schlüsselsuchen als gültig bekannt sind, dh nicht undefined . In diesem Fall wäre es nicht wünschenswert, dass Werttypen undefined . Um dies zu beheben, kann möglicherweise eine Kontrollflussanalyse verwendet werden.

Ist das das offene Problem oder verstehe ich das falsch?

Welche Anwendungsfälle und Probleme habe ich nicht erwähnt?

Es gibt (mindestens) zwei weitere Probleme beim Einfügen von |undefined in Ihre Objekttypdefinitionen:

  • es bedeutet, dass Sie diesen Objekten undefined zuordnen können, was definitiv nicht beabsichtigt ist
  • andere Operationen wie Object.values ​​(oder _.values) erfordern, dass Sie undefined in den Ergebnissen behandeln

Ich denke, das ist ein sehr wichtiger Punkt.

Momentan experimentiere ich mit folgendem Ansatz:

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

Greifen Sie dann auf Eigenschaften wie safelyAccessProperty(myObject, myKey) statt myObject[myKey] .

@plul Guter Fang. Die Diskussion konzentriert sich derzeit auf Leseoperationen, aber die Definition des Indexertyps ist tatsächlich zweigeteilt, und das Hinzufügen von | undefined würde das Schreiben von undefined Werten ermöglichen.

Die safelyAccessProperty Funktion, mit der Sie experimentieren ( oben als getKey von @OliverJAsh erwähnt) erfordert Disziplin und/oder eine Linter-Regel, um Indizierungsoperationen für alle Arrays und Objekte zu verbieten.

Dies kann skalierbar gemacht werden, wenn die Funktion auf allen Array- und Objektinstanzen (jedem Typ, der Indexeroperationen bereitstellt) bereitgestellt wird, wie in C++ std::vector .at() was zur Laufzeit eine Ausnahme für den OOB-Zugriff auslöst , und ein ungeprüfter [] Operator, der im besten Fall mit SEGFAULT beim OOB-Zugriff abstürzt, im schlimmsten Fall den Speicher beschädigt.

Ich denke, das OOB-Zugriffsproblem ist in TypeScript/JavaScript allein auf der Typdefinitionsebene nicht lösbar und erfordert Sprachunterstützung, um potenziell gefährliche Indexeroperationen einzuschränken, wenn diese Strengefunktion aktiviert ist.

Die zweifache Natur des Indexers könnte als Eigenschaft mit get und set Operationen als Funktionen modelliert werden, aber das wäre eine grundlegende Änderung für alle vorhandenen Indexer-Typdefinitionen.

Eine Idee, die vielversprechend zu sein scheint: Was wäre, wenn Sie ?: könnten, wenn Sie eine Indexsignatur definieren, um anzuzeigen, dass Sie erwarten, dass bei normaler Verwendung manchmal Werte fehlen? Es würde sich wie oben erwähnt | undefined verhalten, aber ohne die unangenehmen Nachteile. Es müsste explizite undefined -Werte nicht zulassen, was meiner Meinung nach ein Unterschied zu den üblichen ?: .

Es würde so aussehen:

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'];

Im Vergleich zum vorherigen Ansatz von | 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'];

Ich bin hierher gekommen, weil ich das Hinzufügen von | undefined in der DT-PR oben abgelehnt habe, da dies alle vorhandenen Benutzer dieser API brechen würde - könnte dies besser so betrachtet werden, dass der Benutzer wählen kann, wie pingelig er sein möchte, oder? als die Bibliothek?


Ich werde bemerken, dass optionale Eigenschaften auch | undefined hinzufügen, und das hat mich ein paar Mal gebissen - im Wesentlichen unterscheidet TS nicht zwischen einer fehlenden Eigenschaft und einer Eigenschaft, die auf undefiniert gesetzt ist. Ich möchte nur, dass { foo?: T, bar?: T } genauso behandelt wird wie { [name: 'foo' | 'bar']: T } , wie auch immer das geht (siehe auch die Kommentare zu process.env oben).


Ist TS dagegen, die Symmetrie hier bei Zahlen- und Zeichenfolgenindexern zu brechen?

foo[bar] && foo[bar].baz() ist ein sehr verbreitetes JS-Muster, es fühlt sich ungeschickt an, wenn es nicht von TS unterstützt wird (in dem Sinne, dass Sie daran erinnert werden, dass Sie es tun müssen, wenn Sie | undefined nicht hinzufügen, und warnen, wenn es ist offensichtlich nicht erforderlich, wenn Sie dies tun).


In Bezug auf mutierende Arrays während der Iteration, die die Guard-Ausdruck-Garantie brechen, ist dies auch mit den anderen Guards möglich:

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

aber ich denke, das ist in echtem Code viel weniger wahrscheinlich, dass alte Schleifen den Iterierten mutieren.

Es ist klar, dass diese Funktion nachgefragt wird. Ich hoffe wirklich, dass das TS-Team eine Lösung findet. Nicht nur, indem man | undefined zum Indexer hinzufügt, weil es seine eigenen Probleme hat (bereits erwähnt), sondern auf "cleverere" Weise (Lesen gibt T|undefined , Schreiben erfordert T , guter Compiler Überprüfung auf for Schleife usw. gute Vorschläge wurden auch bereits erwähnt.)

Wir sind mit Laufzeitfehlern einverstanden, wenn wir mit Arrays mutieren und mit Arrays arbeiten, die nicht trivial sind, die durch den Compiler schwer zu überprüfen sind. Wir wollen für die meisten Fälle nur eine Fehlerprüfung und können manchmal ! .

Wenn jedoch diese strengere Behandlung von Arrays implementiert würde, wäre es jetzt mit #24897 möglich, eine nette Typverengung zu implementieren, wenn die Array-Länge mit Konstante überprüft wird. Wir könnten das Array einfach auf ein Tupel mit dem Rest-Element verengen.

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?]
  }
}

Dies wäre nützlich, wenn Sie nach Konstanten indizieren oder ein Array destrukturieren.

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'.",
}

Eine andere Frage ist, was wir mit großen Zahlen machen würden wie:

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

Wir können den Typ dieses riesigen Tupels in IDE- oder Compiler-Nachrichten nicht anzeigen.

Ich denke, wir könnten eine Syntax haben, um "Tupel bestimmter Länge mit demselben Typ für alle Elemente" darzustellen. Zum Beispiel ist das Tupel von 1000 Zeichenfolgen string[10000] und der Typ des eingeengten Arrays aus dem obigen Beispiel könnte [...string[99999], ...string[]] .

Die andere Sorge ist, ob die Compiler-Infrastruktur jetzt so große Tupel unterstützen kann und wenn nicht, wie schwierig dies wäre.

Objekte

Ich möchte immer einen Indextyp von [key: string (or number, symbol)]: V | undefined , nur manchmal vergesse ich den Fall undefined . Immer wenn ein Entwickler dem Compiler explizit sagen muss "Vertrauen Sie mir, das ist wirklich die Art von diesem Ding", wissen Sie, dass es unsicher ist.
Es macht sehr wenig Sinn, Map.get richtig (streng) einzugeben, aber irgendwie bekommen einfache Objekte einen Freipass.
Dies ist jedoch im Benutzerland leicht zu beheben, also ist es nicht so schlimm. Ich habe jedenfalls keine Lösung.

Arrays

Vielleicht übersehe ich etwas, aber es scheint, dass das Argument "Sie fast nie auf unsichere Weise auf ein Array zugreifen" in beide Richtungen gehen kann, insbesondere mit einem neuen Compiler-Flag.

Ich neige dazu zu denken, dass immer mehr Menschen diesen beiden Best Practices folgen:

  • Verwenden Sie funktionale native Methoden oder Bibliotheken, um Arrays zu iterieren oder umzuwandeln. Kein Zugriff auf die Klammern hier.
  • Arrays nicht an Ort und Stelle mutieren

Vor diesem Hintergrund würden die einzigen verbleibenden und seltenen Fälle, in denen Sie eine Low-Level-Bracket-Zugriffslogik benötigen, wirklich von der Typsicherheit profitieren.

Dies scheint ein Kinderspiel zu sein und ich denke nicht, dass das lokale Kopieren und Einfügen der gesamten lib.d.ts eine akzeptable Problemumgehung ist.

Wenn wir explizit in ein Array/Objekt indizieren, zB um das erste Element eines Arrays ( array[0] ) zu erhalten, möchten wir, dass das Ergebnis undefined .

Dies ist durch Hinzufügen von undefined zur Indexsignatur möglich.

Wenn ich das richtig verstehe, besteht das Problem bei der Aufnahme von undefined in die Indexsignatur jedoch darin, dass beim Mapping über das Array oder Objekt die Werte undefined , obwohl bekannt ist, dass dies nicht der Fall ist undefined (sonst würden wir sie nicht zuordnen).

Indextypen/Signaturen werden sowohl für Indexsuchen (zB array[0] ) als auch für das Mapping (zB for Schleifen und Array.prototype.map ) verwendet, aber wir benötigen für jeden dieser Fälle unterschiedliche Typen.

Dies ist bei Map kein Problem, da Map.get eine Funktion ist und daher sein Rückgabetyp vom inneren Werttyp getrennt sein kann, im Gegensatz zur Indizierung in ein Array/Objekt, das keine Funktion ist. und verwendet daher direkt die Indexsignatur.

Es stellt sich also die Frage: Wie können wir beide Fälle erfüllen?

// 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! :-(
})

Vorschlag

Eine Compiler-Option, die Index-Lookups (zB array[0] ) ähnlich wie Set.get und Map.get , indem undefined in den Indexwerttyp eingeschlossen wird (nicht die Indexsignatur selbst). Die tatsächliche Indexsignatur selbst würde undefined , so dass die Zuordnungsfunktionen nicht beeinflusst werden.

Beispiel:

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 :-)
})

Dies löst jedoch nicht den Fall des Schleifens über Arrays/Objekte mit einer for Schleife, da diese Technik Index-Lookups verwendet.

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

Für mich und ich vermute viele andere, ist dies akzeptabel, da for Schleifen nicht verwendet werden, sondern lieber einen funktionalen Stil verwenden, zB Array.prototype.map . Wenn wir sie verwenden müssten, würden wir gerne die hier vorgeschlagene Compiler-Option zusammen mit Nicht-Null-Assertion-Operatoren verwenden.

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

Wir könnten auch eine Möglichkeit zum Opt-in oder Opt-out bieten, zB mit einer Syntax, um die Indexsignatur zu schmücken (bitte verzeihen Sie die mehrdeutige Syntax, die ich für das Beispiel gefunden habe). Diese Syntax wäre nur ein Weg, um zu signalisieren, welches Verhalten wir für Index-Lookups wünschen.

Opt-out (Compiler-Option aktiviert standardmäßig, Opt-out bei Bedarf):

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

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

Opt-in (keine Compiler-Option, nur bei Bedarf anmelden):

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

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

Ich habe mich nicht über dieses Problem oder die Vor- und Nachteile, verschiedene Vorschläge usw. informiert (ich habe es gerade in Google gefunden, nachdem ich wiederholt überrascht war, dass der Array-/Objektzugriff nicht konsequent mit strengen Nullprüfungen gehandhabt wird), aber ich habe eine Verwandter Vorschlag: eine Option, um die Array-Typ-Inferenz so streng wie möglich zu machen, es sei denn, sie wird ausdrücklich überschrieben.

Zum Beispiel:

const balls = [1, 2 ,3];

Standardmäßig wird balls als [number, number, number] . Dies kann durch Schreiben überschrieben werden:

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

Außerdem würde der Zugriff auf Tupelelemente konsistent mit strengen Nullprüfungen gehandhabt. Es überrascht mich, dass im folgenden Beispiel n derzeit als number selbst wenn strikte Nullprüfungen aktiviert sind.

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

Ich würde auch erwarten, dass Array-Mutationsmethoden wie .push in der Tupeltypdefinition nicht vorhanden sind, da solche Methoden den Laufzeittyp so ändern, dass er mit dem Kompilierzeittyp inkonsistent ist.

Schön! Nun, das ist ein interessanter Zeitpunkt. Ich hatte die Release-Ankündigung geöffnet, aber noch nicht durchgelesen; kam erst hierher, nachdem ich in eine Situation geraten war, in der ich ein seltsames Casting ( (<(T|undefined)[]> arr).slice(-1)[0] ) durchführen musste, um TypeScript (2.9) dazu zu bringen, das zu tun, was ich wollte.

Wollte nur auf diesen Vorschlag zurückkommen: https://github.com/Microsoft/TypeScript/issues/13778#issuecomment -383072468

Dies würde die Probleme mit indizierten Typen beheben, die ich erlebt habe. Es wäre großartig, wenn es die Standardeinstellung wäre, aber ich verstehe, dass dies in der realen Welt viele Dinge zerstören würde.

@mhegazy @RyanCavanaugh Irgendwelche Gedanken zu meinem Vorschlag? https://github.com/Microsoft/TypeScript/issues/13778#issuecomment -406316164

Für mich gibt es eine einfache Lösung. Aktivieren Sie ein Flag, das dies überprüft. Dann statt:

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

Sie machen:

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

Bei wahlfreien Indexzugriffen müssen Sie dann prüfen, ob das Ergebnis undefiniert ist, jedoch nicht beim Durchlaufen einer Aufzählungssammlung.

Ich denke immer noch, dass dies rechtfertigt, ein offenes Thema zu haben.

Als Programmierer, der mit TypeScript noch nicht vertraut ist, fand ich die Situation bei der Indizierung von Objekten im strikten Modus nicht intuitiv. Ich hätte erwartet, dass das Ergebnis einer Suche T | undefined , ähnlich wie Map.get . Wie auch immer, ich bin gerade erst darauf gestoßen und habe ein Problem in einer Bibliothek geöffnet:

Screeper/getippte Screeps#107

Ich werde es wahrscheinlich jetzt schließen, weil es anscheinend keine gute Lösung gibt. Ich denke, ich werde versuchen, T | undefined zu "optieren", indem ich eine kleine Hilfsfunktion verwende:

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

Es gab hier einige Vorschläge, T | undefined explizit als Rückgabetyp der Objektindexoperation festzulegen, aber das scheint nicht zu funktionieren:

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

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

Dies ist die VSCode-Version 1.31.1

@yawaramin Stellen Sie sicher, dass strictNullChecks in Ihrem tsconfig.json aktiviert ist (was auch durch das strict Flag aktiviert wird)

Wenn Ihr Anwendungsfall eine willkürliche Indizierung auf Arrays unbekannter Länge erfordert, lohnt es sich meiner Meinung nach, undefined explizit hinzuzufügen (wenn auch nur, um diese Unsicherheit zu "dokumentieren").

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

Tupel funktionieren für kleine Arrays bekannter Länge. Vielleicht könnten wir so etwas wie string[5] als Abkürzung für [string, string, string, string, string] ?

Als Option sehr dafür. Es ist eine auffällige Lücke im Typsystem, insbesondere wenn das Flag strictNullChecks aktiviert ist. Einfache Objekte werden in JS die ganze Zeit als Maps verwendet, daher sollte TypeScript diesen Anwendungsfall unterstützen.

Stolperte mit der Zerstörung eines Funktionsparameters durch das Array:

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

Hier würde ich erwarten, dass a vom Typ string | undefined aber es ist nur string , es sei denn, ich tue es

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

Ich glaube nicht, dass wir eine einzelne for Schleife in unserer Codebasis haben, also würde ich gerne einfach ein Flag zu den Compileroptionen meiner tsconfig hinzufügen (könnte strictIndexSignatures heißen), um dieses Verhalten umzuschalten für unser Projekt.

So habe ich dieses Problem umgangen :

Gute Problemumgehung, ich hoffe wirklich, dass dies vom TypeScript-Team Sherlock wird.

Wenn ein Programmierer in geschriebenem Code Annahmen macht und der Compiler nicht ableiten kann, dass er sicher ist, sollte dies zu einem Compilerfehler führen, es sei denn, er wird IMHO zum Schweigen gebracht.

Dieses Verhalten wäre auch in Kombination mit dem neuen optionalen Verkettungsoperator sehr hilfreich.

Ich bin heute auf ein Problem mit der Verwendung von | undefined mit Karte gestoßen, während ich Object.entries() .

Ich habe einen Indextyp, der ziemlich gut durch {[key: string]: string[]} , mit der offensichtlichen Einschränkung, dass nicht jeder mögliche Zeichenfolgenschlüssel im Index vertreten ist. Ich habe jedoch einen Fehler geschrieben, den TS nicht erkannte, als er versuchte, einen aus dem Index nachgeschlagenen Wert zu konsumieren; behandelte den undefinierten Fall nicht.

Also habe ich es wie empfohlen in {[key: string]: string[] | undefined} geändert, aber jetzt führt dies zu Problemen mit meiner Verwendung von Object.entries() . TypeScript geht jetzt (angemessenerweise basierend auf der Typspezifikation) davon aus, dass der Index möglicherweise Schlüssel mit dem Wert undefined angegeben hat, und nimmt daher an, dass das Ergebnis des Aufrufs von Object.entries() möglicherweise undefinierte Werte enthält.

Ich weiß jedoch, dass dies unmöglich ist; Das einzige Mal, dass ich ein Ergebnis von undefined ist, nach einem Schlüssel zu suchen, der nicht existiert und der bei Verwendung von Object.entries() nicht aufgelistet würde. Um TypeScript glücklich zu machen, muss ich entweder Code schreiben, der keine wirkliche Existenzberechtigung hat, oder die Warnungen überschreiben, was ich lieber nicht tun würde.

@RyanCavanaugh , ich Ihre ursprüngliche Antwort darauf immer noch die aktuelle Position des TS-Teams ist? Sowohl als Anstoß dazu, weil niemand den Thread liest, als auch die Überprüfung, ob noch ein paar Jahre Erfahrung mit leistungsfähigeren JS-Sammlungsprimitiven erforderlich sind, und die Einführung mehrerer anderer Optionen zur Erhöhung der Strenge haben seitdem alles geändert.

(Die Beispiele dort überzeugen mich noch etwas nicht, aber dieser Thread hat schon alle Argumente gemacht, ein weiterer Kommentar wird nichts ändern)

Wenn jemand seine lib.d.ts ändern und alle Downstream-Brüche in seinem eigenen Code beheben und zeigen möchte, wie der Gesamtunterschied aussieht, um zu zeigen, dass dies ein Wertversprechen hat, sind wir für diese Daten offen.

@RyanCavanaugh Hier sind einige der Fälle aus meiner Codebasis, in denen einige Laufzeitabstürze schlummern. Beachten Sie, dass ich bereits Fälle hatte, in denen ich deswegen in der Produktion abstürzte und einen Hotfix veröffentlichen musste.

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))

In diesem Fall wäre es ärgerlich, da ArticleIDs extends articleNames[] undefined in die resultierenden Werte einbezieht, während es nur vollständig definierte Teilmengen zulassen sollte. Leicht zu beheben, indem Sie ReadonlyArray<articleNames> anstelle von 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> => {...}

Alles in allem würde ich mir diese zusätzliche Typsicherheit wirklich wünschen, um mögliche Laufzeitabstürze zu verhindern.

Ich bin in diesem Kommentar #11238 (Kommentar) etwas mehr auf die Argumentation

Denken Sie an die beiden Arten von Schlüsseln in der Welt: Diejenigen, von denen Sie wissen, dass sie _eine entsprechende Eigenschaft in einem Objekt haben (sicher), diejenigen, von denen Sie _nicht_ wissen, dass sie eine entsprechende Eigenschaft in einem Objekt haben (gefährlich).

Sie erhalten die erste Art von Schlüssel, einen "sicheren" Schlüssel, indem Sie den richtigen Code schreiben wie

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

oder

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

Sie erhalten die zweite Art von Schlüssel, die "gefährliche" Art, von Dingen wie Benutzereingaben oder zufälligen JSON-Dateien von der Festplatte oder einer Liste von Schlüsseln, die möglicherweise vorhanden, aber nicht vorhanden sind.

Wenn Sie also einen gefährlichen Schlüssel haben und danach indexieren, wäre es schön, hier | undefined zu haben. Aber der Vorschlag lautet nicht "Behandle _gefährliche_ Schlüssel als gefährlich", sondern "Behandle _alle_ Schlüssel, auch sichere, als gefährlich". Und sobald Sie anfangen, sichere Schlüssel als gefährlich zu behandeln, ist das Leben wirklich scheiße. Du schreibst Code wie

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

und TypeScript beschwert sich über Sie, dass arr[i] undefined , obwohl _hey, ich habe gerade @#%#getestet_. Jetzt haben Sie sich angewöhnt, Code wie diesen zu schreiben, und es fühlt sich dumm an:

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

Oder vielleicht schreiben Sie Code wie diesen:

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

und TypeScript sagt "Hey, dieser Indexausdruck könnte | undefined , also beheben Sie ihn pflichtbewusst, weil Sie diesen Fehler bereits 800 Mal gesehen haben:

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

Aber du hast den Fehler nicht behoben . Sie wollten Object.keys(yourObj) oder vielleicht myObj[k] schreiben. Das ist der schlimmste Compilerfehler, weil er Ihnen in keinem Szenario wirklich hilft - es wird nur das gleiche Ritual auf jede Art von Ausdruck angewendet, ohne Rücksicht darauf, ob es tatsächlich gefährlicher war als jeder andere Ausdruck der gleichen Form.

Ich denke an das alte "Möchten Sie diese Datei wirklich löschen?" Dialog. Wenn dieser Dialog jedes Mal erscheinen würde, wenn Sie versuchten, eine Datei zu löschen, würden Sie sehr schnell lernen, del y wenn Sie früher del gedrückt haben, und Ihre Chancen, etwas Wichtiges nicht zu löschen, werden auf den vorherigen Wert zurückgesetzt -Dialog-Grundlinie. Wenn der Dialog stattdessen nur beim Löschen von Dateien angezeigt wurde, die nicht in den Papierkorb wanderten, haben Sie jetzt eine sinnvolle Sicherheit. Aber wir haben keine Ahnung (und könnten auch wir nicht), ob Ihre Objektschlüssel sicher sind oder nicht. Dialog jedes Mal, wenn Sie dies tun, werden Fehler wahrscheinlich nicht besser gefunden, als wenn nicht alle angezeigt werden.

Ich stimme der Analogie zum Dialog zum Löschen von Dateien zu, aber ich denke, dass diese Analogie auch so erweitert werden kann, dass der Benutzer gezwungen wird, etwas zu überprüfen, das möglicherweise nicht definiert oder null ist. Daher macht diese Erklärung nicht wirklich Sinn, denn wenn diese Erklärung wahr ist, die Option strictNullChecks wird das gleiche Verhalten induzieren, zum Beispiel ein Element aus dem DOM mit document.getElementById .

Aber das ist nicht der Fall, viele TypeScript-Benutzer möchten, dass der Compiler ein Flag für solchen Code auslöst, damit diese Randfälle angemessen behandelt werden können, anstatt den Cannot access property X of undefined Fehler auszulösen, der sehr, sehr schwer zu verfolgen ist.

Am Ende hoffe ich, dass diese Art von Funktionen als zusätzliche TypeScript-Compiler-Optionen implementiert werden können, denn das ist der Grund, warum Benutzer TypeScript verwenden möchten, sie möchten vor gefährlichem Code gewarnt werden.

Es ist unwahrscheinlich, dass man über den falschen Zugriff auf Arrays oder Objekte spricht. Haben Sie Daten, um diese Behauptung zu sichern? Oder basiert es nur auf einem willkürlichen Bauchgefühl?

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

Die kontrollflussbasierte Typanalyse von TypeScript könnte verbessert werden, um diesen Fall zu erkennen, um sicher zu sein und ! nicht erforderlich zu machen. Wenn ein Mensch daraus schließen kann, dass es sicher ist, kann das auch ein Compiler.
Mir ist bewusst, dass die Implementierung im Compiler möglicherweise nicht trivial ist.

Die kontrollflussbasierte Typanalyse von TypeScript könnte verbessert werden, um diesen Fall als sicher zu erkennen

Es kann wirklich nicht.

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

Übergibt diese Funktion beim zweiten Durchlauf der Schleife ein undefined an someFunc oder nicht? Es gibt eine Menge Dinge, die ich in someFunc schreiben könnte, die später dazu führen würden, dass undefined auftaucht.

Was ist damit?

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 lassen Sie mich ein anderes Beispiel geben:

```
$ Knoten

const arr = []
nicht definiert
arr[7] = 7
7
arr
[ <7 leere Elemente>, 7]
für (lassen Sie i = 0; i < arr.length; i++) {
... console.log(arr[i])
... }
nicht definiert
nicht definiert
nicht definiert
nicht definiert
nicht definiert
nicht definiert
nicht definiert
7
undefiniert```

@RyanCavanaugh, da Sie hier sind, wie wäre es, item :: T für arr :: T[] in for (const item of arr) ... ableiten und ansonsten arr[i] :: T | undefined ableiten, wenn Sie etwas --strict-index ? Wie wäre es mit dem Fall, der mir wichtig ist, obj[key] :: V | undefined aber Object.values(obj) :: V[] für obj :: { [key: string]: V } ?

@yawaramin Wenn Sie spärliche Arrays verwenden, macht Typescript bereits nicht das Richtige. Ein --strict-index Flag würde das beheben. Dies kompiliert:

const arr = []
arr[7] = 7

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

@RyanCavanaugh Es gibt ein sehr häufiges Beispiel, das ich Ihnen zeigen kann, bei dem Benutzer dazu neigen, falsch auf das Array zuzugreifen.

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

Der obige Code sollte die Compilerprüfung nicht ordnungsgemäß unter strictNullChecks , da einige Verwendungen von getBlock undefiniert zurückgeben, zum Beispiel getBlock('hello') Compiler das Raise-Flag, damit ich die undefinierten Fälle elegant behandeln kann, ohne meine Anwendung zu explodieren.

Und dies gilt auch für viele gängige Redewendungen wie den Zugriff auf das letzte Element eines Arrays mit arr.slice(-1)[0] , den Zugriff auf das erste Element arr[0] usw.

Letztendlich möchte ich, dass TypeScript mich für solche Fehler ärgert, anstatt sich mit explodierten Anwendungen herumschlagen zu müssen.

Übergibt diese Funktion beim zweiten Durchlauf der Schleife ein undefined an someFunc, oder nicht? Es gibt eine Menge Dinge, die ich in someFunc schreiben könnte, die später zu einem undefinierten Auftauchen führen würden.

@RyanCavanaugh Ja, JavaScript ! oder ein ReadonlyArray Array erforderlich sein: someFunc(arr: ReadonlyArray<number>, i: number) .

@yawaramin Bei spärlichen Arrays muss der Elementtyp wahrscheinlich undefined sei denn, TypeScript kann daraus schließen, dass er wie ein Tupel verwendet wird. In dem Code, mit dem @danielnixon verlinkt ist (https://github.com/microsoft/TypeScript/issues/13778#issuecomment-536248028), werden Tupel ebenfalls speziell behandelt und enthalten nicht undefined im zurückgegebenen Elementtyp da der Compiler garantiert, dass nur auf gesetzte Indizes zugegriffen wird.

das ist eine bewusste entscheidung. Es wäre sehr ärgerlich, wenn dieser Code ein Fehler wäre:

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

Hey, ich kenne diese Syntax; Ich schreibe solche Loops vielleicht einmal im Jahr!

Ich habe festgestellt, dass in den meisten Fällen, in denen ich tatsächlich in ein Array indiziere, ich danach tatsächlich nach undefined suchen möchte.

Die Befürchtungen, die Bearbeitung dieses speziellen Falles zu erschweren, scheinen übertrieben. Wenn Sie nur über die Elemente iterieren möchten, können Sie for .. of . Wenn Sie den Elementindex aus irgendeinem Grund benötigen, verwenden Sie forEach oder durchlaufen Sie entries . Im Allgemeinen ist es äußerst selten, dass Sie wirklich eine indexbasierte for-Schleife benötigen.

Wenn es bessere Gründe gibt, warum man den Status quo haben möchte, würde ich sie gerne sehen, aber unabhängig davon: Dies ist eine Inkonsistenz im System und eine Flagge zu deren Behebung würde von vielen anscheinend sehr geschätzt.

Hallo zusammen, ich habe das Gefühl, dass ein Großteil der Diskussion hier stattgefunden hat
hatte. Die Paketbesitzer haben ihre Argumentation ziemlich klar ausgedrückt und
haben die meisten dieser Argumente bereits berücksichtigt. Wenn sie vorhaben zu adressieren
Ich bin sicher, sie werden es bekannt geben. Ansonsten glaube ich das nicht
Thread ist wirklich produktiv.

Am Freitag, 25.10.2019 um 11:59 Uhr schrieb brunnerh [email protected] :

das ist eine bewusste entscheidung. Es wäre sehr ärgerlich, wenn dieser Code
ein Fehler sein:

var a = [];for (var i =0; i< a.length; i++) {
a[i]+=1; // a[i] ist möglicherweise undefiniert
}

Hey, ich kenne diese Syntax; Ich schreibe solche Loops vielleicht einmal im Jahr!

Ich habe festgestellt, dass in den meisten Fällen, in denen ich tatsächlich in ein Array indiziere, ich
eigentlich nach undefined suchen wollen.

Die Befürchtungen, die Bearbeitung dieses speziellen Falles zu erschweren, scheinen übertrieben.
Wenn Sie nur über die Elemente iterieren möchten, können Sie für .. of. Wenn
Sie benötigen den Elementindex aus irgendeinem Grund, verwenden Sie forEach oder iterieren Sie über
Einträge. Im Allgemeinen ist es äußerst selten, dass Sie wirklich eine
indexbasierte for-Schleife.

Wenn es bessere Gründe gibt, warum man den Status Quo haben möchte, würde ich es tun
sehen sie gerne, aber egal: Das ist eine Inkonsistenz im System
und eine Flagge zu haben, um es zu reparieren, würde von vielen sehr geschätzt werden, wie es scheint.


Sie erhalten dies, weil Sie einen Kommentar abgegeben haben.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/microsoft/TypeScript/issues/13778?email_source=notifications&email_token=ACAJU3DQ7U6Y3MUUM26J4JDQQM62XA5CNFSM4C6KEKAKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63DQ7U6Y4
oder abmelden
https://github.com/notifications/unsubscribe-auth/ACAJU3EWVM3CUFG25UF5PGDQQM62XANCNFSM4C6KEKAA
.

Ich habe das Gefühl, dass es zwar keine Bewegung von Paketbesitzern gibt, aber kontinuierliches Feedback aus der Community ist immer noch wertvoll, da es zeigt, dass immer noch Bedarf an besseren Tools für dieses Problem besteht.

@brunnerh Ich stimme dir zu, heutzutage brauche ich nicht einmal for Schleifen zu verwenden, es sei denn, es ist zur Leistungsoptimierung, was 0% der Zeit in der Codebasis meiner Firma passiert, weil sich die meiste Zeit die Karte ändert /filter/reduce to for loop verbessert selten die Leistung, der wahre Schuldige liegt immer in ineffizienter Logik, Netzwerkproblemen und Datenbankverbindungen.

Ich bin überrascht, dass noch niemand über as const hat.

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'.

Allgemeiner und nicht genau in Bezug auf die ursprüngliche Nachricht dieses Problems habe ich in den letzten Monaten die folgenden defensiven Programmiermuster verwendet, und es hat gut funktioniert (für mich)

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
}

Auch wenn es keine kurze Notation dafür gibt (wie ? ), finde ich es in Ordnung. Dem Compiler selbst mitzuteilen, dass Sie möglicherweise nicht sicher sind, ob der Wert definiert ist, ist völlig in Ordnung.

@martpie as const ist großartig, wenn Sie es verwenden können.

Es gibt mindestens zwei Gründe, warum ich Array<T | undefined> lieber nicht verwenden würde:

  1. Es ist eine weitere Sache, die Sie jedes Mal beachten müssen, wenn Sie ein Array verwenden. Außerdem können Sie nicht mehr einfach implizites Tippen verwenden, was praktisch ist.
  2. Es ändert die Signaturen von forEach , map und filter , die undefined als Elementargument übergeben (es sei denn, der Index ist explizit so festgelegt). Je nachdem, wie oft Sie diese Funktionen nutzen, kann der Umgang damit nervig sein.

Ich möchte auch einläuten, dass dies jetzt viele falsch positive Ergebnisse in eslint/typescript verursacht:

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

Eslint leitet (richtigerweise) vom Typ ab, dass dies eine unnötige Prüfung ist, da foo niemals null sein kann. Jetzt muss ich also nicht nur bewusst daran denken, eine Nullprüfung für Dinge durchzuführen, die ich von einem Array zurückbekomme, ich muss _auch_ eine eslint-Disable-Zeile hinzufügen! Und der Zugriff auf Dinge außerhalb einer solchen for-Schleife ist praktisch die einzige Möglichkeit, auf Arrays zuzugreifen, da wir (wie wahrscheinlich die meisten TS-Entwickler heutzutage) forEach/map/filter/etc-Funktionen verwenden, wenn wir über Arrays schleifen.

Ich denke wirklich, wir sollten einfach ein neues Compiler-Flag haben, das standardmäßig auf true gesetzt ist, wenn strict wahr ist, und wenn es den Leuten nicht gefällt, können sie sich abmelden. Wir haben in den letzten Versionen sowieso neue strenge Compilerprüfungen hinzugefügt.

Dies ist die Quelle für wahrscheinlich die _nur_ Runtime-Prod-Bugs, die ich im letzten Speicher gesehen habe, die ein Versagen des Typsystems waren.

In Anbetracht der Ergonomie des jetzt erhältlichen optionalen Verkettungsoperators ist es vielleicht an der Zeit, die Entscheidung zu überdenken, dieses Verhalten _nicht_ über ein Flag verfügbar zu machen?

Wenn ich ein Array von Thing vom Server bekomme und die letzten Thing im Array anzeigen muss, stoße ich häufig darauf, dass das Array tatsächlich leer ist und auf eine Eigenschaft zugreift dabei stürzt Thing die App ab.

Beispiel:

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

Vielleicht ist dies ein Problem mit dem Design unserer API, aber es fühlt sich wirklich so an, als ob TS verstehen sollte, dass wenn Sie auf etwas im Array zugreifen, es möglicherweise nicht wirklich vorhanden ist.

Bearbeiten: Ich möchte nur klarstellen, dass ich "unsere API" meinte, wie in "die API, die von dem Team erstellt wurde, an dem ich arbeite"

Ja, alle sind sich ziemlich einig, dass es eine sehr schlechte Designentscheidung ist, eine Zeitgenosse zu einer fernen Zeit, als TS völlig unsicher war.

Als Workaround verwende ich einen kleinen Dirty-Hack. Ich füge einfach package.json das einfache Postinstall-Skript hinzu:

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

Natürlich funktioniert es nicht unter Windows, aber das ist besser als nichts.

Alle anderen Probleme, die neben diesem ausgeführt werden, scheinen hierher umgeleitet zu werden, daher gehe ich davon aus, dass dies der beste Ort ist, um zu fragen: Ist eine Abkürzungssyntax für optionale Indexsignaturen von der Tabelle? Wie @martpie darauf hinweist, müssen Sie interface Foo { [k: string]: Bar | undefined; } schreiben, was weniger ergonomisch ist als interface Foo { [k: string]?: Bar; } .

Können wir ?: Operator-Support erhalten? Wenn nicht, warum nicht? Das heißt, der ergonomische Vorteil war ausreichend, um die Funktion für die Definition einzelner Eigenschaften hinzuzufügen - ist sie nicht "hilfreich genug" für Indexsignaturen?

type Foo = { [_ in string]?: Bar } funktioniert auch. Nicht so hübsch, aber ziemlich knapp. Könnte auch Ihren eigenen Dict Typ erstellen, wenn Sie möchten. Würde aber nicht gegen eine ?: Erweiterung argumentieren

Das fühlt sich an wie einer dieser "Javascript: The Bad Parts"-Gespräche:

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

Die Verwendung von T | undefined für die Typsignatur ist wirklich nicht sinnvoll. Wir brauchen einen Weg , um es so zu machen , dass der Index - Operator [n] einen hat T|undefined Typen, aber zB mit map auf einem Array sollte uns nicht geben T|undefined Werte, denn in dieser Situation sollten wir wissen, dass sie existieren.

@radix Für die eigentlichen Funktionen auf Array denke ich nicht, dass dies ein Problem sein wird, da sie alle ihre eigenen Typdefinitionen haben, die den richtigen Typ ausgeben: zB map : https://github.com/microsoft /TypeScript/blob/master/lib/lib.es5.d.ts#L1331

Die einzige übliche Codeverwendung, bei der das | undefined Konstrukt eine tatsächliche Regression der Erfahrung darstellt, ist in for ... of Schleifen. Leider (aus der Perspektive dieses Themas) sind dies ziemlich häufige Konstrukte.

@riggs Ich denke, du redest davon, interface Array<T> { } [index: number]: T | undefined , aber @radix spricht wahrscheinlich über die aktuelle Empfehlung, die darin besteht, Array<T | undefined> zu verwenden Ihren eigenen Code.

Letzteres ist aus mehreren Gründen schlecht, nicht zuletzt, weil Sie die Typen anderer Pakete nicht kontrollieren können, aber ersteres hat auch einige Probleme, nämlich dass Sie dem Array undefined zuweisen können, und dass es gibt undefined auch in bekannten sicheren Fällen. ️

Ahh, ja, mein Missverständnis. Ich bezog mich tatsächlich nur auf die Verwendung der Definition [index: number]: T | undefined . Ich stimme voll und ganz zu, dass die Definition eines Typs als Array<T | undefined> eine schreckliche

Gibt es eine elegante Möglichkeit, lib.es5.d.ts zu überschreiben, oder ist ein Postinstall-Skript der beste Weg?

@ nicu-Chiciuc https://www.npmjs.com/package/patch-package passt vollständig in Werkzeugkasten eines Ketzers neben https://www.npmjs.com/package/yalc ✌️

Gibt es eine elegante Möglichkeit, lib.es5.d.ts zu überschreiben, oder ist ein Postinstall-Skript der beste Weg?

In unserem Projekt haben wir eine global.d.ts Datei, die wir verwenden, um 1) Typendefinitionen für integrierte APIs hinzuzufügen, die noch nicht in den Standardtypdefinitionen von Typescript enthalten sind (z. B. WebRTC-bezogene APIs, die sich ständig ändern und über Browser inkonsistent sind ) und 2) Überschreiben einiger Standardtypdefinitionen von typescript (zB Überschreiben des Typs von Object.entries sodass Arrays zurückgegeben werden, die unknown anstelle von any ).

Ich bin mir nicht sicher, ob dieser Ansatz verwendet werden könnte, um die Array-Typen von Typescript zu überschreiben, um dieses Problem zu lösen, aber es könnte einen Versuch wert sein.

Das obige funktioniert, wenn das Zusammenführen von Deklarationen das gewünschte Ergebnis liefert, aber zur Vereinfachung schneiden sie Schnittstellen, aber hier ist die weniger restriktive Option das, was Sie wollen.

Sie könnten stattdessen copy-Einfügen alle die versuchen lib.*.d.ts Dateien , die Sie verwenden in Ihrem Projekt enthalten in tsconfig.json ‚s files oder include , dann bearbeiten sie zu dem, was Sie wollen. Da sie /// <reference no-default-lib="true"/> , sollten Sie keine andere Magie benötigen, aber ich bin mir nicht sicher, ob Sie die /// <reference lib="..."/> sie haben, von ihren Abhängigkeiten entfernen müssen. Dies ist natürlich aus all den offensichtlichen Gründen der Wartbarkeit schlecht.

@butchler Wir verwenden diesen Ansatz auch, aber es schien, dass das Überschreiben des Indexierungsoperators nicht möglich war, obwohl wir einige andere Array-Funktionen ( filter mit Typwächtern) erfolgreich überschrieben haben.

Ich habe einen seltsamen Fall beim Überschreiben des Indexierungsoperators:

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

Die zweite Zuweisung irrt (wie sie sollte), die erste jedoch nicht.
Noch seltsamer ist, dass das Schweben über first während der Destrukturierung zeigt, dass es den Typ string | undefined aber das Schweben darüber, während es zugewiesen wird, zeigt, dass es den Typ string .
Screenshot 2020-02-05 at 10 40 25 AM
Screenshot 2020-02-05 at 10 40 32 AM

Gibt es eine andere Typdefinition für die Destrukturierung von Arrays?

Suchen Sie nach einer Lösung dafür, da Fehler im Zusammenhang mit fehlenden Indizes eine häufige Fehlerquelle in meinem Code waren.

Die Angabe eines Typs wie { [index: string]: string | undefined } ist keine Lösung, da es die Eingabe von Iteratoren wie Object.values(x).forEach(...) durcheinander bringt, die niemals undefined Werte enthalten.

Ich möchte, dass TypeScript Fehler generiert, wenn ich nicht nach undefined suche, nachdem ich someObject[someKey] , aber nicht, wenn Object.values(someObject).forEach(...) .

@danielnixon Das ist keine Lösung, das ist ein Workaround. Nichts hindert Sie oder einen anderen Entwickler daran, irrtümlicherweise (wenn Sie es überhaupt so nennen können) die integrierten Tools der Sprache für die gleichen Ziele zu verwenden. Ich meine, ich benutze fp-ts für dieses Zeug, aber dieses Problem ist immer noch gültig und muss behoben werden.

Während ich den Kontra-Argumenten von @RyanCavanaugh folgen kann mit:

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

Ich würde ein paar kurze Gedanken dazu machen. Im Kern zielt TypeScript darauf ab, die Entwicklung, nun ja... sicherer zu machen. Um sich an das erste Ziel von TypeScript zu erinnern:

1. Konstrukte, bei denen es sich wahrscheinlich um Fehler handelt, statisch identifizieren.

Wenn wir uns den Compiler ansehen, haben wir bereits einige Magie hinter der Typinferenz. Im konkreten Fall lautet die wirkliche Entschuldigung: "Wir können aus diesem Konstrukt nicht den richtigen Typ ableiten" und das ist völlig in Ordnung. Im Laufe der Zeit haben wir immer weniger Fälle, in denen compilieren nicht in der Lage ist, die Typen abzuleiten. Mit anderen Worten, für mich ist es nur eine Frage der Zeit (und der damit verbundenen Zeit), um eine ausgefeiltere Typinferenz zu erhalten.

Aus technischer Sicht sind Konstruktionen wie:

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

bricht das erste Ziel von TypeScript. Dies geschieht jedoch nicht, wenn TypeScript die genaueren Typen kennt:

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)

Aus praktischer Sicht ist es ein Kompromiss yet . Diese Ausgabe und mehrere geschlossene Ausgaben zeigen, dass eine hohe Nachfrage der Community nach dieser Änderung besteht: ATM 238 Upvoters vs. 2 Downvoters. Natürlich ist es schade, dass die obige for-Schleife nicht auf den "richtigen" Typ hinweist, aber ich bin mir ziemlich sicher, dass die meisten Upvoter mit ! und dem neuen ? as leben können Zeichen der Aufmerksamkeit und erzwingen Sie es in sicheren Fällen. Aber auf der anderen Seite bekommen die richtigen Typen auf "gefährliche" Zugriffe.

Da ich zustimme, dass es sich um eine sehr sensible Änderung für große Codebasen handelt, stimme ich für eine Konfigurationseigenschaft, wie sie hier vorgeschlagen wurde . Zumindest um Feedback von der Community zu bekommen. Wenn es nicht möglich ist, dann sollte TS 4.0 es bekommen.

Dies ist keine vorgeschlagene Lösung, sondern nur ein Experiment: Ich habe versucht, lib.es5.d.ts in meinem node_modules in einem bestehenden Projekt zu ändern, das TypeScript verwendet, nur um zu sehen, wie es wäre, wenn wir einen Compiler _hätten_ hätten Möglichkeit dazu. Ich habe Array und ReadonlyArray geändert:

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

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

Da es sich um ein sehr großes Projekt handelt, hat dies mehrere hundert Schreibfehler verursacht. Ich habe nur ein paar von ihnen durchgesehen, um eine Vorstellung davon zu bekommen, auf welche Art von Problemen ich stoßen würde und wie schwierig es wäre, sie zu umgehen, wenn es eine Compiler-Option dafür gäbe. Hier sind einige der Probleme, auf die ich gestoßen bin:

  1. Dies führte nicht nur zu Typfehlern in unserer Codebasis, sondern auch in einer unserer Abhängigkeiten: io-ts. Da Sie mit io-ts Typen erstellen können, die Sie in Ihrem eigenen Code verwenden, ist es meiner Meinung nach nicht möglich, diese Option nur auf Ihre eigene Codebasis und nicht auch auf die Typen von io-ts anzuwenden. Dies bedeutet, dass io-ts und wahrscheinlich einige andere Bibliotheken noch aktualisiert werden müssten, um mit dieser Option zu funktionieren, selbst wenn sie nur als Compiler-Option eingeführt würde. Zuerst dachte ich, dass dies weniger umstritten wäre, wenn dies eine Compiler-Option wäre, aber wenn Leute, die sich für die Option entscheiden, sich bei einer Reihe verschiedener Bibliotheksautoren über Inkompatibilitäten beschweren, kann dies noch umstrittener sein, als einfach nur ein TS 4.0 zu sein brechende Veränderung.

  2. Manchmal musste ich einige zusätzliche Typenwächter hinzufügen, um die Möglichkeit von Undefinierten auszuschließen. Dies ist nicht unbedingt eine schlechte Sache und ist der ganze Sinn dieses Vorschlags, aber ich erwähne es nur der Vollständigkeit halber.

  3. Ich hatte einen Typfehler in einer for (let i = 0; i < array.length; i++) -Schleife, die über ein beliebiges Array<T> . Ich konnte nicht einfach einen Typschutz hinzufügen, um nach undefined , da T selbst undefined . Die einzigen Lösungen, die mir einfielen, waren A) eine Typzusicherung oder @ts-ignore zu verwenden, um den

  4. Es gibt viele Fälle, in denen der vorhandene Code bereits eine Aussage über den Wert von .length und dann auf ein Element im Array zugegriffen hat. Diese verursachten jetzt trotz der .length Prüfung Typfehler, sodass ich entweder den Code ändern musste, um nicht von einer .length Prüfung abhängig zu sein, oder einfach eine redundante !== undefined Prüfung hinzufügen musste. Es wäre wirklich schön, wenn TypeScript es irgendwie zulassen könnte, einen .length Check zu verwenden, um den !== undefined Check zu vermeiden. Ich gehe jedoch davon aus, dass es nicht trivial wäre, das tatsächlich umzusetzen.

  5. Einige Codes verwendeten A[number] , um den Typ der Elemente eines generischen Array-Typs abzurufen. Dies gab jedoch jetzt T | undefined statt nur T , was an anderer Stelle zu Typfehlern führte. Ich habe einen Helfer erstellt, um dies zu umgehen:

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

    Aber selbst mit diesem Helfer, der den Übergang erleichtert, ist dies immer noch eine große bahnbrechende Änderung. Vielleicht könnte TypeScript eine Art Sonderfall für A[number] , um dieses Problem zu vermeiden, aber es wäre schön, wenn möglich, seltsame Sonderfälle wie diesen zu vermeiden.

Ich habe nur eine kleine Handvoll der Hunderte von Typfehlern durchgesehen, daher ist dies wahrscheinlich eine sehr unvollständige Liste.

Für alle anderen, die daran interessiert sind, dieses Problem zu beheben, kann es nützlich sein, dasselbe mit einigen vorhandenen Projekten zu versuchen und mitzuteilen, auf welche anderen Probleme Sie stoßen. Hoffentlich können die spezifischen Beispiele jedem, der tatsächlich versucht, dies umzusetzen, eine Orientierungshilfe bieten.

@butchler Ich bin auch einige dieser Probleme something[i] als something(i) anzuzeigen , das heißt, ich kann nicht einfach so etwas wie if (Meteor.user() && Meteor.user()._id) {...} , also tue ich es nicht Erwarten Sie diesen Ansatz nicht für die Array-Indizierung; Ich muss zuerst den Wert aus dem Array abrufen, den ich untersuchen möchte. Was ich damit sagen möchte, ist, dass das Verlassen auf TS zu verstehen, dass das Überprüfen der Längeneigenschaft und das Aufstellen einer darauf basierenden Behauptung das Typsystem zu stark belasten könnte. Eine Sache, die mich dazu gebracht hat, den Übergang zu verschieben (neben der Tatsache, dass einige andere Bibliotheken auch Fehler haben, wie Sie gesagt haben), ist, dass die Destrukturierung von Arrays noch nicht richtig funktioniert und der Wert destructured nicht T | undefined aber T (siehe meinen anderen Kommentar).
Abgesehen davon halte ich das Überschreiben von lib.es5.d.ts für einen guten Ansatz. Selbst wenn sich die Dinge in Zukunft ändern werden, führt das Zurücksetzen der Änderung nicht zu zusätzlichen Fehlern, sondern stellt sicher, dass einige Randfälle bereits abgedeckt sind.
Wir haben bereits begonnen, patch-package zu verwenden, um einige Typdefinitionen in react zu ändern, und dieser Ansatz scheint gut zu funktionieren.

Ich bin auch einige dieser Probleme durchgegangen, ich habe angefangen, etwas[i] als etwas(i) anzusehen, das heißt, ich kann nicht einfach so etwas verwenden wie if (Meteor.user() && Meteor.user()._id) { ...}, daher erwarte ich diesen Ansatz nicht für die Array-Indizierung; Ich muss zuerst den Wert aus dem Array abrufen, den ich untersuchen möchte.

Ja, ich habe diesen Ansatz auch verwendet, als ich mein Experiment ausprobierte. Beispiel: Code, der zuvor geschrieben wurde als:

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

musste in etwas geändert werden wie:

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

Was ich damit sagen möchte, ist, dass das Verlassen auf TS zu verstehen, dass das Überprüfen der Längeneigenschaft und das Aufstellen einer darauf basierenden Behauptung das Typsystem zu stark belasten könnte.

Möglicherweise wahr. Es könnte tatsächlich einfacher sein, einen Codemod zum automatischen Neuschreiben von Code zu schreiben, der von Behauptungen über .length abhängt, um einen anderen Ansatz zu verwenden, als TypeScript intelligent genug zu machen, um mehr über die Typen auf der Grundlage von Behauptungen über das .length

Selbst wenn sich die Dinge in Zukunft ändern werden, führt das Zurücksetzen der Änderung nicht zu zusätzlichen Fehlern, sondern stellt sicher, dass einige Randfälle bereits abgedeckt sind.

Dies ist ein guter Punkt. Während die Aufnahme von undefined in Indextypen eine große bahnbrechende Änderung darstellt, ist der Weg in die andere Richtung keine bahnbrechende Änderung. Code, der in der Lage ist, T | undefined sollte auch T ohne Änderungen verarbeiten können. Dies bedeutet zum Beispiel, dass Bibliotheken aktualisiert werden könnten, um den Fall T | undefined , wenn ein Compiler-Flag vorhanden wäre, und dennoch von Projekten verwendet werden könnten, bei denen dieses Compiler-Flag nicht aktiviert ist.

Ich ignoriere Arrays und konzentriere mich für einen Moment auf Record<string, T> . Meine persönliche Wunschliste ist, dass das Schreiben nur T erlaubt, das Lesen jedoch 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"];

Der einzige Weg, um das oben Genannte ergonomisch zu gestalten, wäre eine Art abhängiges Tippen und abhängige Typenwächter. Da wir diese nicht haben, würde das Hinzufügen dieses Verhaltens alle möglichen Probleme verursachen.

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

Zurück zu Arrays: Das Problem besteht darin, dass die Indexsignatur für Arrays nicht dieselbe Absicht wie Record<number, T> .

Sie benötigen also ganz andere abhängige Schutzvorrichtungen, wie z.

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

Die Indexsignatur für Arrays ist also nicht wirklich Record<number, T> . Es ist eher wie Record<(int & (0 <= i < this["length"]), T> (ein bereichs- und nummergestützter Integer-Typ)


Während der ursprüngliche Beitrag also nur von Arrays spricht, scheint der Titel Indexsignaturen von "nur" string oder "nur" number vorzuschlagen. Und es sind zwei völlig unterschiedliche Diskussionen.

TL;DR Ursprünglicher Beitrag und Titel sprechen über verschiedene Dinge, die Implementierung sieht stark abhängig (ha) von abhängiger Typisierung aus.

Angesichts der Tatsache, dass Funktionen wie forEach , map , filter usw. existieren (und IMHO viel vorzuziehen sind), würde ich nicht erwarten, dass das TS-Team seine Inferenz-Engine stark komplizieren würde Unterstützen Sie das Schleifen über ein Array mit einer regulären for-Schleife. Ich habe das Gefühl, dass es einfach zu viel unerwartete Komplexität gibt, wenn man versucht, dies zu erreichen. Da beispielsweise i keine Konstante ist, was passiert, wenn jemand den Wert von i innerhalb der Schleife ändert? Sicher, es ist ein Randfall, aber es ist etwas, das sie (hoffentlich) intuitiv handhaben müssen.

Das Korrigieren von Indexsignaturen sollte jedoch relativ einfach sein, wie die obigen Kommentare gezeigt haben.

4. Es gibt viele Fälle, in denen der vorhandene Code bereits eine Aussage über den Wert von .length und dann auf ein Element im Array zugreift. Diese verursachten nun trotz der .length Prüfung Typfehler, sodass ich entweder den Code ändern musste, um nicht von einer .length Prüfung abhängig zu sein, oder einfach eine redundante !== undefined Prüfung hinzufügen musste. Es wäre wirklich schön, wenn TypeScript es irgendwie zulassen könnte, einen .length Check zu verwenden, um den !== undefined Check zu vermeiden. Ich gehe jedoch davon aus, dass es nicht trivial wäre, das tatsächlich umzusetzen.

@metzger
Aus Neugier, wie viele davon greifen mit einer sich ändernden Variablen zu oder mit einer festen Nummer? Denn im letzteren Fall verwenden Sie das Array vermutlich als Tupel, für das TS die Array-Länge verfolgt. Wenn Sie häufig Arrays als Tupel verwenden, wäre ich gespannt, was eine erneute Eingabe als tatsächliche Tupel mit der Anzahl der Fehler ausmacht.

Wenn Sie häufig Arrays als Tupel verwenden, wäre ich gespannt, was eine erneute Eingabe als tatsächliche Tupel mit der Anzahl der Fehler ausmacht.

Ich bin auf einen Fall gestoßen, in dem ich einen Typfehler behoben habe, indem ich einfach ein as const zu einem Array-Literal hinzugefügt habe, um den Typ der Variablen in ein Tupel zu verwandeln. Ich habe jedoch absolut keine Ahnung, wie häufig das in unserer Codebasis vorkommt, da ich nur etwa 10 von mehreren hundert Fehlern betrachtet habe.

Damit bin ich heute auch auf ein Problem gestoßen.
Wir greifen von einem Array über einen berechneten Index zu. Dieser Index könnte außerhalb des zulässigen Bereichs liegen.
Wir haben also so etwas in unserem Code:

const noNext = !items[currentIndex + 1];

Dies führt dazu, dass noNext als false . Was falsch ist. Es kann wahr sein.
Ich möchte items als Array<Item | undefined> weil das eine falsche Erwartung ergibt.
Wenn ein Index vorhanden ist, sollte er niemals undefined . Aber wenn Sie den falschen Index verwenden, _ist_ er undefined .
Sicher, das obige könnte wahrscheinlich gelöst werden, indem Sie stattdessen einen .length Check verwenden oder noNext explizit als boolean .
Aber am Ende stört mich das, seit ich mit TypeScript angefangen habe und ich habe nie verstanden, warum | undefined standardmäßig nicht enthalten ist.

Da ich erwarte, dass die meisten typescript-eslint verwenden, gibt es eine Regel, die erzwingen könnte, dass Werte nach der Indizierung überprüft werden müssen, bevor sie verwendet werden können? Dies kann hilfreich sein, bevor die Unterstützung von TS implementiert wird.

Falls es jemanden interessieren könnte. In einem früheren Kommentar habe ich erwähnt, dass der Ansatz des Patchens der Array Indexierungsdefinition in lib.es5.d.ts ein seltsames Verhalten bei der Destrukturierung von Arrays hat, da es anscheinend nicht von der Änderung beeinflusst wurde. Es scheint, dass es von der Option target in tsconfig.json abhängt und es funktioniert korrekt, wenn es es5 (https://github.com/microsoft/TypeScript/issues/37045 ).

Für unser Projekt ist das kein Problem, da wir die Option noEmit und die Transpilation von meteor abgewickelt wird. Aber für Projekte, bei denen dies ein Problem sein könnte, ist eine Lösung, die möglicherweise funktionieren könnte, safe-array-destructuring (https://github.com/typescript-eslint/typescript-eslint/pull/1645).
Es ist immer noch ein Entwurf und es ist möglicherweise nicht von Wert, wenn das gesamte Problem im TypeScript-Compiler behoben wird, aber wenn Sie denken, dass es nützlich sein könnte, können Sie alle Bedenken/Verbesserungen in der typescript-eslint Regel PR ansprechen

Leider unterstützt der Fix mit eslint in Ihrem PR nur Tupel und keine Arrays. Das Hauptproblem und die Hauptauswirkung, die ich damit habe, liegt in der Destrukturierung von Arrays.

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

Ich denke, das ganze Thema ging auf eine Seite, wo ich anderer Meinung bin. Ich denke nicht, dass es erzwungene Überprüfungen innerhalb von ForEach, Karte usw. geben sollte. Nichtsdestotrotz halte ich dies immer noch für entscheidend, um die Destrukturierung von Arrays zu unterstützen.

Nun, es ist nur ein Fehler bei der Destrukturierung von Arrays, sodass Sie gezwungen sind, dies mithilfe der Indizierung zu tun.

Ist das doch nicht schlimm? Die Array-Destrukturierung ist eine erstaunliche Funktion. Das Problem ist, dass die Eingaben nicht korrekt sind. Das Deaktivieren der Funktion ist keine wirklich gute Lösung. Die Verwendung von Indizes ist meiner Meinung nach hässlicher (schwerer zu lesen und zu verstehen) und fehleranfälliger.

Es ist hässlicher, aber es zwingt den Entwickler zu überprüfen, ob das Element definiert ist oder nicht. Ich habe versucht, elegantere Lösungen zu finden, aber diese scheinen die wenigsten Nachteile zu haben. Zumindest für unseren Anwendungsfall.

@caseyhoward Wie bereits in dieser Ausgabe erwähnt, führt dies zu unerwünschtem Verhalten bei den verschiedenen Array.prototype-Funktionen:

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

Dies muss in den array.prototype-Funktionen nicht behoben werden. Dies ist ein massives Problem für die Destrukturierung von Arrays!

Bin heute wieder darauf gestoßen. Ich denke immer noch, der richtige Kompromiss ist dieser , zu dem ich gerne ein Feedback bekommen würde.

Ein weiterer Anwendungsfall für dieses Problem – ich bin auf dieses Problem gestoßen, als ich typescript-eslint verwendet und no-unnecessary-condition aktiviert hat. In der Vergangenheit haben wir beim Zugriff auf ein Array über den Index, um eine Operation mit dem Element an diesem Index auszuführen, eine optionale Verkettung verwendet, um sicherzustellen, dass dieser Index definiert ist (falls der Index außerhalb der Grenzen liegt), wie in array[i]?.doSomething() . Bei dem in dieser Ausgabe beschriebenen Problem markiert no-unnecessary-condition diese optionale Verkettung jedoch als unnötig (da der Typ laut Typoskript nicht nullfähig ist) und der Autofix entfernt die optionale Verkettung, was zu Laufzeitfehlern führt, wenn der Array-Zugriff auf der Index i tatsächlich ist nicht definiert.

Ohne diese Funktion wurde meine Anwendung sehr fehlerhaft, da ich mit mehrdimensionalen Arrays zu tun habe, ich muss mich immer daran erinnern, auf Elemente mit xs[i]?.[j] statt xs[i][j] zuzugreifen, außerdem muss ich explizit casten das Element, auf das zugegriffen wird, wie const element = xs[i]?.[j] as Element | undefined , um die Typsicherheit zu gewährleisten.

Diese Begegnung war mein erster großer WTF in Typescript. Die Sprache habe ich ansonsten als wunderbar zuverlässig empfunden und das hat mich im Stich gelassen, und es ist ziemlich umständlich, "as T | undefined" zu Array-Zugriffen hinzuzufügen. Würde gerne einen der Vorschläge umgesetzt sehen.

Ja, hier das gleiche. Es hat uns gezwungen, unsere eigenen Typen mit ( | undefined )

Sie sind sich nicht sicher, was Sie meinen, können Sie das Problem verlinken oder ein Beispiel zeigen?

Am Mi, 29. April 2020 um 02:41 Kirill Groshkov [email protected]
schrieb:

Ja, hier das gleiche. Es hat uns gezwungen, unsere eigenen Typen mit ( |
undefined), um Typsicherheit zu haben. Hauptsächlich für den Objektzugriff (wahrscheinlich ist es ein
separates geöffnetes Problem), aber die gleiche Logik gilt für den Zugriff auf Array-Indizes.


Sie erhalten dies, weil Sie einen Kommentar abgegeben haben.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/microsoft/TypeScript/issues/13778#issuecomment-621096030 ,
oder abmelden
https://github.com/notifications/unsubscribe-auth/AAAGFJJT37N54I7EH2QLBYDRO7Y4NANCNFSM4C6KEKAA
.

@alangpierce

Eine Idee, die vielversprechend zu sein scheint: Was wäre, wenn Sie ?: könnten, wenn Sie eine Indexsignatur definieren, um anzuzeigen, dass Sie erwarten, dass bei normaler Verwendung manchmal Werte fehlen? Es würde sich wie oben erwähnt | undefined verhalten, aber ohne die unangenehmen Nachteile. Es müsste explizite undefined -Werte nicht zulassen, was meiner Meinung nach ein Unterschied zu den üblichen ?: .

Das ist eine interessante Idee, die in die richtige Richtung weist. Ich würde jedoch die Logik umkehren: Der Standardfall sollte sein, dass Indexsignaturen | undefined . Wenn Sie dem Compiler mitteilen möchten, dass die undefinierte Groß-/Kleinschreibung in Ihrem Code nie vorkommen darf (damit Sie nie auf ungültige Schlüssel zugreifen), können Sie !: wie

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

t['foo'] // Has type string

und der Standardfall ohne !: würde so aussehen, wie wir ihn kennen, wäre aber sicherer:

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

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

Auf diese Weise haben Sie standardmäßig die Sicherheit und müssen gleichzeitig nicht explizit undefined was es ermöglichen würde, versehentlich undefined zu schreiben.

Sorry, falls das schon vorgeschlagen wurde, ich konnte nicht alle Kommentare in diesem Thread lesen.

Im Allgemeinen denke ich wirklich, dass das Standardverhalten, das wir jetzt haben, nicht das ist, was Benutzer bei der Verwendung von TypeScript erwarten. Es ist besser, auf Nummer sicher zu gehen, besonders wenn ein Fehler so einfach wie dieser vom Compiler abgefangen werden kann.

Ich konnte die index.d.ts nicht dazu bringen, mit meinem Projekt zu arbeiten und wollte mich nicht wirklich damit befassen.

Ich habe diesen kleinen Hack erstellt, der einen Typschutz erzwingt:

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

Obwohl der Typ am Ende wie folgt lautet: 0 | T , erfüllt er dennoch seine Aufgabe.

Würde const firstNode = nodes?.[0] funktionieren?

@ricklove, warum hast du +!undefined einfach 1 vorgezogen?

Würde const firstNode = nodes?.[0] funktionieren?

Nein, da Typoskript (meiner Meinung nach fälschlicherweise) es nicht als optional behandelt (siehe #35139).

Laut der Flow-Dokumentation ist das Verhalten bei Indexsignaturen das gleiche wie derzeit bei TypeScript:

Wenn ein Objekttyp über eine Indexereigenschaft verfügt, wird davon ausgegangen, dass Eigenschaftszugriffe den annotierten Typ haben, selbst wenn das Objekt zur Laufzeit keinen Wert in diesem Slot hat. Es liegt in der Verantwortung des Programmierers, sicherzustellen, dass der Zugriff wie bei Arrays sicher ist.

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

In dieser Hinsicht verlangen sowohl TS als auch Flow, dass der Benutzer an undefinierte Schlüssel denkt, anstatt dass der Compiler ihnen hilft :(

Wäre ein Vorteil von TS, wenn der Compiler Benutzer daran hindern würde, solche Fehler zu machen. Wenn man alle Kommentare in diesem Thread liest, scheint es, dass die überwiegende Mehrheit hier diese Funktion gerne hätte. Ist das TS-Team immer noch zu 100% dagegen?

Es erscheint albern, dass TS sein Bestes tut, um den Zugriff auf undefinierte Mitglieder zu verhindern, außer hier. Dies ist bei weitem der häufigste Fehler, den ich in unserer Codebasis behoben habe.

[ bewegende Diskussion hier von #38470]
Akzeptieren Sie alle Argumente, warum dies für Arrays nicht praktikabel wäre.
Ich denke, dies sollte auf jeden Fall für Tupel angegangen werden:

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

warum machst du nicht foo string | number | undefined ?

Wenn Sie dies Tupeln geben, sollte dies nicht die Mehrheit der Benutzer beeinflussen, und die Benutzer, die davon betroffen sind, müssen strictNullChecks aktiviert haben und ihre Arrays als Konstanten deklarieren ...
Sie suchen offensichtlich nach strengerer Typensicherheit.

könnte auch dabei helfen

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

Was ursprünglich meinen Laufzeitfehler verursachte, da ein number zu einem NaN und dann ein undefined aus einem Tupel zurückgab.
All das war typsicher

Ich bin mir nicht sicher, ob Tupel wirklich so unterschiedlich sind. Sie müssen Pausentypen berücksichtigen (zB [str, ...num[]] ), und technisch gesehen ist aa[Infinity] absolut gültiges Javascript, daher könnte ich mir vorstellen, dass es kompliziert wird, einen speziellen Fall für sie herauszuarbeiten.

Ihr Beitrag hat mich auch dazu gebracht, darüber nachzudenken, wie es aussehen würde, wenn wir Unterstützung dafür bekommen würden, Index-Returns als undefiniert zu behandeln. Wenn, wie in Ihrem Beispiel, aa[num] tatsächlich als string | number | undefined eingegeben würde, müsste ich dann for (let i = 0; i < 2; i++) { foo(aa[i]!); } schreiben, obwohl ich weiß, dass der Index in Grenzen bleibt? Wenn strenge Nullprüfungen etwas, das ich geschrieben habe, markieren, möchte ich dies beheben können, indem ich entweder Dinge besser eintippe oder Laufzeitwächter verwende – wenn ich auf eine Nicht-Null-Assertion zurückgreifen muss, bin ich normalerweise sehe es als ein Versagen meinerseits an. Ich sehe in diesem Fall jedoch keinen Weg, das zu umgehen, und das stört mich.

Wenn tuple[number] immer in T | undefined eingegeben wird, wie möchten Sie dann den Fall behandeln, in dem Sie wissen, dass der Index korrekt begrenzt ist?

@thw0rted Ich kann mich nicht erinnern, for Schleife in Typescript verwendet habe ( .map und .reduce ftw). Deshalb wäre eine Compiler-Option dafür imo großartig (die standardmäßig deaktiviert sein kann). Viele Projekte stoßen nie auf etwas wie den von Ihnen angegebenen Fall und verwenden Indexsignaturen nur zum Destrukturieren von Arrays usw.

(mein ursprünglicher Kommentar: https://github.com/microsoft/TypeScript/issues/13778#issuecomment-517759210)

Oh ganz ehrlich, ich indexiere auch selten mit einer for-Schleife. Ich mache ausgiebigen Gebrauch von Object.entries oder Array#map , aber hin und wieder muss ich Schlüssel für die Indizierung in ein Objekt oder (wahrscheinlich noch seltener) Indizes in ein Array oder Tupel übergeben. Die for Schleife war sicherlich ein erfundenes Beispiel, aber sie weist darauf hin, dass jede Option ( undefined oder nicht) Nachteile hat.

aber es wirft den Punkt auf, dass jede Option ( undefined oder nicht) Nachteile hat.

Ja, und es wäre schön, wenn der Benutzer von TypeScript wählen könnte, welchen Nachteil er bevorzugt :wink:

Oh ganz ehrlich, ich indexiere auch selten mit einer for-Schleife. Ich mache ausgiebigen Gebrauch von Object.entries oder Array#map , aber hin und wieder muss ich Schlüssel für die Indizierung in ein Objekt oder (wahrscheinlich noch seltener) Indizes in ein Array oder Tupel übergeben.

In den Fällen, in denen dies erforderlich ist, können Sie Array#entries :

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

Ich persönlich benutze Array#forEach allzu oft, und ich benutze Array#map nie für Iterationen (ich benutze es jedoch die ganze Zeit für das Mapping). Ich ziehe es vor, meinen Code flach zu halten, und ich schätze die Möglichkeit, aus einer for...of-Schleife auszubrechen.

Ich bin mir nicht sicher, ob Tupel wirklich so unterschiedlich sind. Sie müssen Pausentypen berücksichtigen (zB [str, ...num[]] ), und technisch gesehen ist aa[Infinity] absolut gültiges Javascript, daher könnte ich mir vorstellen, dass es kompliziert wird, einen speziellen Fall für sie herauszuarbeiten.

Ihr Beitrag hat mich auch dazu gebracht, darüber nachzudenken, wie es aussehen würde, wenn wir Unterstützung dafür bekommen würden, Index-Returns als undefiniert zu behandeln. Wenn, wie in Ihrem Beispiel, aa[num] tatsächlich als string | number | undefined eingegeben würde, müsste ich dann for (let i = 0; i < 2; i++) { foo(aa[i]!); } schreiben, obwohl ich weiß, dass der Index in Grenzen bleibt? Wenn strenge Nullprüfungen etwas, das ich geschrieben habe, markieren, möchte ich dies beheben können, indem ich entweder Dinge besser eintippe oder Laufzeitwächter verwende – wenn ich auf eine Nicht-Null-Assertion zurückgreifen muss, bin ich normalerweise sehe es als ein Versagen meinerseits an. Ich sehe in diesem Fall jedoch keinen Weg, das zu umgehen, und das stört mich.

Wenn tuple[number] immer in T | undefined eingegeben wird, wie möchten Sie dann den Fall behandeln, in dem Sie wissen, dass der Index korrekt begrenzt ist?

Ich bin mir nicht sicher, ob so viel for(let i..) über Tupel iteriert.
Wenn das Tupel einen einzelnen Typ hat, dann würde der Benutzer wahrscheinlich ein Array verwenden,

Meiner Erfahrung nach eignen sich Tupel hervorragend für gemischte Typen und wenn dies der allgemeine Fall ist, dann überprüfen Sie sowieso die Typen

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

Auch wenn diese vorgeschlagene Änderung passiert, was hindert die Leute daran, dies zu tun?

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

Sie könnten keyof typeof aa anstelle von 0 | 1 wenn sie dieses Problem beheben

Sicher würde es keine Typsicherheit geben, wenn man i arithmetisch macht,
aber es erlaubt den Leuten wirklich, dies für die Typsicherheit gegenüber dem Standardarray "Nicht unbedingt typsicher, aber bequem" zu wählen

Ich denke, mit den Operatoren ?? und ?. ist es nicht mehr allzu umständlich , wahlfrei auf Array s zuzugreifen.
Aber auch, es ist wahrscheinlich viel häufiger zu

  • iterieren (z. B. mit .map oder .forEach , wo " T | undefined " nicht benötigt wird, wird der Callback nie bei Out-of-Bounds-Indizes ausgeführt) oder
  • durchlaufen die Array in Schleifen, von denen einige statisch als sicher bestimmt werden können ( for...of .. fast, außer für _sparse arrays_, for...in meiner Meinung nach sicher).

Außerdem könnte eine Variable als "sicherer Index" angesehen werden, wenn sie zuerst mit <index> in <array> überprüft wird.


_Erklärung_: Ich spreche über den ursprünglichen Grund dafür, dass Array<T> nicht den Indextyp [number]: T | undefined (sondern nur [number]: T ), nämlich dass die Verwendung zu umständlich wäre.
Ich meine, dass es nicht mehr der Fall ist, also könnten undefined hinzugefügt werden.

@vp2177 Das Problem sind nicht die Funktionen (ja, ?. funktioniert), das Problem ist, dass der Compiler uns nicht davor warnt und verhindert, dass wir uns in den Fuß schießen.

Vielleicht könnte es eine Linting-Regel tun, aber immerhin.

ja, ?. funktioniert

Ich bin anderer Ansicht. ?. funktioniert für den Zugriff auf Eigenschaften des indizierten Werts, aber der Compiler sagt immer noch, dass der Wert definiert ist, unabhängig davon, ob er definiert ist oder nicht (siehe #35139).

Wenn Sie eslint verwenden und no-unnecessary-condition verwenden möchten, werden solche Zugriffe als unnötig markiert und entfernt, da der Compiler sagt, dass es nie undefiniert ist und das ?. unnötig machen würde.

@martpie Entschuldigung, ich glaube, Sie haben meinen Kommentar falsch verstanden und eine Klarstellung hinzugefügt.

Ich meine... scheint eine gute Idee zu sein, ein --strictIndexChecks oder ähnliches zu haben, das es den Leuten ermöglicht, sich für diese Art von Verhalten zu entscheiden. Es wird dann nicht für alle eine bahnbrechende Änderung sein und es gibt eine strengere Typumgebung.

Ich glaube, dass Flow standardmäßig für Indizes auf diese Weise funktioniert und die oben genannten Probleme nicht aufweist, aber es ist eine Weile her, dass ich es verwendet habe, also könnte ich mich irren.

@bradennapier Flow verwendet T | undefined nicht in der Indexsignatur für Array s (also leider dasselbe wie TypeScript.)

Ich habe zuvor einen Ansatz vorgeschlagen, der jeden glücklich machen könnte. Ich hoffe, es ist in Ordnung, es mit anderen Worten zu wiederholen.

Grundsätzlich würde das Standardverhalten der Indexsignatur geändert zu

  • Fügen Sie | undefined wenn Sie aus einem Array oder Objekt lesen
  • Fügen Sie beim Schreiben nicht | undefined (da wir nur T Werte in unserem Objekt haben wollen)

Definition wäre so:

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

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

Für Leute, die nicht möchten, dass undefined im Typ enthalten ist, können sie es entweder mit einer Compiler-Option deaktivieren oder es nur für einige Typen mit dem ! Operator deaktivieren:

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

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

Um bestehenden Code nicht zu zerstören, ist es möglicherweise auch besser, eine neue Compileroption einzuführen, die bewirkt, dass undefined eingeschlossen wird (wie oben mit dem MaybeUndefined Typ gezeigt). Wenn diese Option nicht angegeben wird, verhalten sich alle Indexsignaturtypen so, als ob der Operator ! in der Deklaration verwendet wurde.

Eine weitere Idee, die auftaucht: Vielleicht möchten Sie an einigen Stellen im Code denselben Typ mit den beiden unterschiedlichen Verhaltensweisen verwenden (einschließlich undefined oder nicht). Eine neue Typzuordnung könnte definiert werden:

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

Wäre das eine gute Erweiterung von TypeScript?

Ich mag die Idee, aber ich vermute, wir müssten zuerst https://github.com/microsoft/TypeScript/issues/2521 bekommen - was, versteh mich nicht falsch, ich glaube immer noch, wir sollten es unbedingt tun, aber ich' Ich halte meinen Atem nicht an.

@bradennapier Flow verwendet T | undefined nicht in der Indexsignatur für Array s (also leider dasselbe wie TypeScript.)

Hmm, was ist mit Objektindizes? Ich bin noch nie auf die Notwendigkeit von Arrays gestoßen, aber ziemlich sicher, dass Sie bei der Verwendung von Objekten überprüfen müssen.

@bradennapier Objektindizes? Wie in o[4] wobei o für object ? TypeScript erlaubt Ihnen das nicht.....

Ausdruck vom Typ '4' kann nicht verwendet werden, um den Typ '{}' zu indizieren
Eigenschaft '4' existiert nicht für Typ '{}'.ts(7053)

@RDGthree nicht

Mit Ausnahme von strictNullChecks haben wir keine Flags, die das Typsystemverhalten ändern. Flags aktivieren/deaktivieren normalerweise die Fehlerberichterstattung.

und etwas später hat RyanCavanaugh:

Wir bleiben ziemlich skeptisch, ob jemand in der Praxis von dieser Flagge profitieren würde. Karten und kartenähnliche Dinge können sich bereits für | . anmelden undefined an ihren Definitions-Sites, und das Erzwingen eines EULA-ähnlichen Verhaltens beim Array-Zugriff scheint kein Gewinn zu sein. Wir müssten wahrscheinlich CFA und Typenwächter erheblich verbessern, um dies schmackhaft zu machen.

Also im Grunde - sie sind sich der Tatsache vollkommen bewusst, dass nach einer Flagge gefragt wird, und bis jetzt waren sie nicht davon überzeugt, dass sich die Arbeit für sie für den eher zweifelhaften Nutzen lohnt, den die Leute daraus ziehen würden. Ehrlich gesagt überrascht es mich nicht, denn hier kommen immer wieder alle mit so ziemlich genau den gleichen Vorschlägen und Beispielen, die bereits angesprochen wurden. Idealerweise verwenden Sie einfach for of oder Methoden für Arrays und Map anstelle von Objekten (oder weniger effektiv 'T | undefinierte' Objektwerte).

Ich bin hier, weil ich ein beliebtes DT-Paket betreibe und die Leute immer wieder darum bitten, dass |undefined zu Dingen wie http-Header-Maps hinzugefügt werden, was völlig vernünftig ist, mit Ausnahme des Teils, in dem es so ziemlich jede bestehende Verwendung unterbrechen würde , und die Tatsache, dass andere sichere Verwendungen wie Object.entries() viel schlechter zu verwenden sind. Abgesehen davon, dass Sie sich darüber beschweren, dass Ihre Meinung besser ist als die der Macher von Typescript, was ist Ihr Beitrag hier?

Simon, vielleicht habe ich deinen Kommentar falsch gelesen, aber es klingt irgendwie wie ein Argument für den ursprünglichen Vorschlag. Die fehlende Funktion "letzte Meile" besteht darin, Eigenschaften manchmal je nach Kontext als | undefined zu behandeln, in anderen Fällen jedoch nicht. Deshalb habe ich eine Analogie zu #2521 Upthread gezogen.

Im Idealfall könnte ich ein Array oder Objekt so deklarieren, dass bei gegebenem

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

Ich kann irgendwie mit enden

  • arr[n] schreibt als T | undefined
  • arr[n] = undefined ist ein Fehler
  • Ich habe Zugriff auf eine Art Iteration auf arr , die mir Werte gibt, die als T eingegeben werden, nicht als T | undefined
  • obj[k] schreibt als V | undefined
  • obj[k] = undefined ist ein Fehler
  • Object.entries() zum Beispiel sollte mir Tupel von [K, V] und nichts kann undefiniert sein

Es ist die asymmetrische Natur des Problems, die dieses Problem aus meiner Sicht definiert.

Ich würde sagen, dass die Aussage, dass es ein Problem für Entwickler ist, die for-Schleifen verwenden, Entwickler nicht berücksichtigt, die Array Destructuring verwenden. Es ist in Typescript defekt und wird aufgrund dieses Problems auch nicht behoben. Es bietet ungenaue Eingaben. Die durchgeführte IMO-Array-Destrukturierung ist ein viel größeres Problem als diejenigen, die noch for-Schleifen verwenden.

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()
}

Ich möchte nicht zu weit vom ursprünglichen Punkt des Problems abweichen, aber Sie sollten diese Beispielmethode wirklich mit einem Tupeltyp für das Funktionsargument deklarieren. Wenn die Funktion als ([userId, duration, ...reason]: [string, number, ...string[]]) => {} deklariert wurde, müssten Sie sich darüber erst gar keine Gedanken machen.

Ich meine, sicher könnte ich es einfach gießen, aber das ist nicht wirklich eine Lösung. In ähnlicher Weise könnte jeder hier einfach die Array-Indizierung der for-Schleife umwandeln und dieses gesamte Problem stumm schalten.

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

Das Problem ist, dass Typescript vor diesem Verhalten nicht warnt. Als Entwickler müssen wir daran denken, diese manuell als undefiniert zu konvertieren, was ein schlechtes Verhalten für ein Tool ist, das den Entwicklern mehr Arbeit verursacht. Um fair zu sein, müsste die Besetzung tatsächlich so aussehen:

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

}

Das ist gar nicht schön. Aber wie ich oben sagte, ist das Hauptproblem, dass es nicht einmal so eingegeben werden muss, sondern dass TS überhaupt nicht davor warnt. Das führt dazu, dass Entwickler dies vergessen, um Code zu pushen, cool CI hat keine Probleme beim Zusammenführen und Bereitstellen von tsc gefunden. TS vermittelt ein Gefühl von Vertrauen in den Code und ungenaue Eingaben geben ein falsches Vertrauen. => Laufzeitfehler!

Das ist kein "Cast", das ist die richtige Eingabe Ihrer formalen Parameter. Mein Punkt war, dass, wenn Sie Ihre Methode richtig eingeben, jeder, der sie unsicher mit der alten Signatur ( args: string[] ) aufgerufen hat, jetzt zur Kompilierzeit Fehler erhält, wie sie es sollten , wenn dies nicht garantiert ist übergeben Sie die richtige Anzahl von Argumenten. Wenn Sie es den Leuten immer wieder erlauben wollen, nicht alle benötigten Argumente zu übergeben und sie selbst zur Laufzeit zu überprüfen, ist es eigentlich sehr einfach, als (args: [string?, number?, ...string[]]) zu schreiben. Das funktioniert schon super und ist für dieses Thema nicht wirklich relevant.

Das würde das Problem nur von dieser Funktion auf eine andere Funktion verschieben, die diese Funktion aufruft. Da Sie die vom Benutzer bereitgestellte Zeichenfolge immer noch in separate Werte parsen müssen, und wenn Sie die Array-Destrukturierung verwenden, um sie innerhalb des CommandHandlers zu analysieren, der diese Funktion aufruft, haben Sie das gleiche Problem.

Es führt kein Weg daran vorbei, ungenaue Typisierungen für die Array-Destrukturierung bereitzustellen.

Ich möchte nicht zu weit vom ursprünglichen Punkt des Problems abweichen

Hinweis: Obwohl ich Ihnen zustimme, sollten sie als separate Probleme behandelt werden. Jedes Mal, wenn jemand ein Problem öffnet, das die Probleme mit der Destrukturierung von Arrays zeigt, wird es geschlossen und Benutzer hierher weitergeleitet. #38259 #36635 #38287 usw... Ich glaube also nicht, dass wir vom Thema dieser Ausgabe abweichen.

Einige meiner Frustrationen mit diesem Problem würden durch eine einfache Syntax gelöst, um | undefined auf den Typ anzuwenden, der ansonsten aus dem Objektzugriff abgeleitet würde. Z.B:

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

Eine strengere Typprüfung bleibt also opt-in. Das Folgende funktioniert, erfordert jedoch eine Duplizierung (und fügt schnell eine ähnliche Menge an visuellem Rauschen hinzu):

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

Ich kann mir vorstellen, dass diese Art von "Typ Assert to Maybe" -Syntax ( as ? ) es auch einfacher machen würde, eine require-safe-index-signature-access eslint-Regel zu implementieren, die für neue Benutzer nicht sehr verwirrend ist. (zB "Sie können den Lint-Fehler beheben, indem Sie am Ende as ? hinzufügen.") Die Regel könnte sogar einen sicheren Autofixer enthalten, da dies nur zum Abbruch der Kompilierung und niemals zu Laufzeitproblemen führen kann.

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

Ich glaube nicht, dass es eine einzige Anwendung davon abgesehen von dieser überraschenden TS-Funktion gibt, obwohl 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

Danke für die Antwort. Während eine Funktion wie diese funktionieren würde (und mit ziemlicher Sicherheit von den meisten JS-Engines wegoptimiert wird), würde ich lieber das erwähnte zusätzliche Syntaxrauschen akzeptieren ( as ComplexType | undefined ), als dass die kompilierte JS-Datei diese Methode "wrapper" behält „Überall, wo es gebraucht wird.

Eine andere Syntaxoption (die weniger anwendungsspezifisch wäre als die as ? oben) könnte darin bestehen, die Verwendung des Schlüsselworts infer in Typzusicherungen als Platzhalter für den ansonsten abgeleiteten Typ zuzulassen. z.B:

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

Derzeit sind infer Deklarationen nur in der extends Klausel eines bedingten Typs ( ts(1338) ) zulässig, sodass das Schlüsselwort infer im Kontext von as [...] eine Bedeutung

Das könnte immer noch sinnvoll sein, die Verwendung von infer | undefined über eine eslint-Regel zu erzwingen, während es für andere Fälle allgemein genug ist, z. B. wenn der resultierende Typ mit Dienstprogrammtypen verfeinert werden kann:

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

Es ist nutzlos, wenn wir uns immer wieder daran erinnern müssen, ein anderes, sichereres Muster zu verwenden. Der Punkt ist, dass TypeScript unseren Rücken stärkt, egal ob wir an einem sehr guten Tag sind, müde oder neu in einer Codebasis sind.

Unser aktuelles Verhalten ist, dass der Typausdruck Array<string>[number] als "Der Typ, der auftritt, wenn Sie ein Array mit einer Zahl indizieren" interpretiert wird. Du darfst schreiben

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

als Umweg, um const t: string schreiben.

Wenn dieses Flag existiert, was sollte passieren?

Wenn Array<string>[number] string | undefined , haben Sie ein Problem mit der Solidität von Schreibvorgängen:

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

Wenn Array<string>[number] string , dann haben Sie das gleiche Problem wie in Reads beschrieben:

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

Es erscheint zu komplex, die Typsyntax sowohl für "Der Indexlesetyp von" als auch "Der Indexschreibtyp von" hinzuzufügen. Die Gedanken?

Ich sollte hinzufügen, dass das Ändern der Bedeutung von Array<string>[number] wirklich problematisch ist, da dies bedeuten würde, dass dieses Flag die Interpretation von Deklarationsdateien ändert. Das ist nervenaufreibend, weil dies die Art von Flag zu sein scheint, die eine beträchtliche Anzahl von Leuten aktivieren würde, aber dazu führen könnte, dass Fehler in Dingen auftreten/nicht auftreten, die von DefinitelyTyped veröffentlicht werden, also müssten wir wahrscheinlich sicherstellen, dass alles erstellt wird sauber in beide Richtungen. Die Anzahl der Flags, die wir mit diesem Verhalten hinzufügen können, ist offensichtlich extrem klein (wir können nicht 64 verschiedene Konfigurationen jedes Pakettyps testen), daher denke ich, dass wir Array<T>[number] als T nur um dieses Problem zu vermeiden.

@RyanCavanaugh In Ihrem Beispiel mit write(arr, undefined) der Aufruf von write akzeptiert, aber würde die Zuweisung arr[0] = v; nicht mehr kompilieren?

Wenn die obige write Funktion aus einer Bibliothek stammt und JS ohne das Flag kompiliert wurde, dann wäre es sicher unzuverlässig. Dies ist jedoch bereits ein bestehendes Problem, da Bibliotheken mit beliebigen Flags kompiliert werden können. Wenn eine Bibliothek ohne strikte Nullprüfungen kompiliert wurde, könnte eine darin enthaltene Funktion string , während sie eigentlich string|undefined . Aber die meisten Bibliotheken scheinen heutzutage Nullprüfungen zu implementieren, sonst beschweren sich die Leute. Sobald dieses neue Flag existiert, beginnen die Bibliotheken hoffentlich damit, es zu implementieren, und schließlich werden die meisten Bibliotheken es gesetzt haben.

Auch wenn ich keine konkreten Beispiele finden kann, musste ich auf jeden Fall skipLibCheck setzen, damit meine App kompiliert werden kann. Es ist eine ziemlich standardmäßige tsconfig-Option in unserem Boilerplate, wenn ein neues TS-Repository erstellt wird, weil wir so viele Probleme damit hatten.

Persönlich (und vielleicht egoistisch) bin ich in Ordnung, wenn es um Randfälle geht, wenn es _my_ Code im Allgemeinen sicherer macht. Ich glaube immer noch, dass ich beim Zugriff auf Arrays über den Index häufiger auf Nullzeiger stoßen würde als in anderen Grenzfällen, obwohl ich vom Gegenteil überzeugt sein könnte.

Wenn Array[Zahl] ist Zeichenfolge | undefined, dann haben Sie ein Problem mit der Solidität von Schreibvorgängen:

Meiner Meinung nach sollte Array<string>[number] immer string|undefined . Es ist die Realität, wenn ich in ein Array mit einer beliebigen Zahl indiziere, erhalte ich entweder den Elementtyp des Arrays oder undefiniert. Sie können dort nicht wirklich genauer sein, es sei denn, Sie codieren die Arraylänge wie bei Tupeln. Ihr Beispiel zum Schreiben würde nicht check eingeben, da Sie string|undefined einem Array-Index nicht zuweisen können.

Es erscheint zu komplex, die Typsyntax sowohl für "Der Indexlesetyp von" als auch "Der Indexschreibtyp von" hinzuzufügen. Die Gedanken?

Dies scheint genau das zu sein, was sein sollte, da es sich um zwei verschiedene Dinge handelt. In keinem Array wird jemals ein Index definiert, sodass Sie immer die Möglichkeit haben, undefiniert zu werden. Der Typ von Array<string>[number] wäre string|undefined . Um zu spezifizieren, was T in einem Array<T> , könnte ein Dienstprogrammtyp verwendet werden (die Benennung ist nicht großartig): ArrayItemType<Array<string>> = string . Dies hilft nicht bei Record Typen, die möglicherweise etwas wie RecordValue<Record<string, number>, string> = string benötigen.

Ich stimme zu, dass es hier keine großartigen Lösungen gibt, aber ich bin mir ziemlich sicher, dass ich die Solidität im Index vorziehen würde.

Ich halte dies bei Arrays nicht besonders für notwendig, da viele andere Sprachen (einschließlich "sicherer" wie Rust) die Verantwortung für die Grenzüberprüfungen dem Benutzer überlassen, und daher sind ich und viele Entwickler bereits daran gewöhnt . Außerdem macht es die Syntax in diesen Fällen ziemlich offensichtlich, weil sie _immer_ Klammernotation wie foo[i] .

Trotzdem _do_ ich es sehr, dies zu string-indizierten Objekten hinzuzufügen, da es sehr schwierig ist zu sagen, ob die Signatur von foo.bar korrekt ist (da das Feld bar explizit definiert ist). oder möglicherweise undefined (weil es Teil einer Indexsignatur ist). Wenn dieser Fall (den ich für ziemlich hochwertig halte) gelöst wird, wird der Array-Fall wahrscheinlich trivial und es lohnt sich wahrscheinlich auch, es zu tun.


Es erscheint zu komplex, die Typsyntax sowohl für "Der Indexlesetyp von" als auch "Der Indexschreibtyp von" hinzuzufügen. Die Gedanken?

Die Getter- und Setter-Syntax von Javascript könnte auf Typdefinitionen erweitert werden. Beispielsweise wird die folgende Syntax von TypeScript vollständig verstanden:

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

TS "verschmelzt" diese jedoch derzeit zu einem einzigen Typ, da es die "get"- und "set"-Typen eines Felds nicht separat verfolgt. Dieser Ansatz funktioniert in den meisten Fällen, hat jedoch seine eigenen Nachteile, z. B. dass Getter und Setter denselben Typ verwenden müssen und einem Feld, das nur einen Setter definiert, fälschlicherweise ein Gettertyp zugewiesen wird.

Wenn TS "get" und "set" separat für alle Felder verfolgen würde (was wahrscheinlich einige echte Leistungskosten verursacht), würde es diese Macken lösen und auch einen Mechanismus zur Beschreibung der in dieser Ausgabe beschriebenen Funktion bereitstellen:

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

würde im Wesentlichen zur Abkürzung für:

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

Ich sollte hinzufügen, dass die Bedeutung von Array geändert wird[Nummer] ist wirklich problematisch, da es bedeuten würde, dass dieses Flag die Interpretation von Deklarationsdateien ändert.

Wenn generierte Deklarationsdateien immer die vollständige Getter+Setter-Syntax verwenden würden, wäre dies _nur_ für handgeschriebene ein Problem. Die Anwendung der aktuellen Regeln auf die verbleibende Abkürzungssyntax _nur in Definitionsdateien_ könnte hier eine Lösung sein. Es würde sicherlich die Kompatibilitätsprobleme lösen, aber auch den mentalen Aufwand erhöhen, .ts Dateien im Vergleich zu .d.ts Dateien zu lesen.


Beiseite:

Dies betrifft natürlich nicht _alle_ die subtilen Vorbehalte mit Gettern/Settern:

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

Dies ist ein weiterer Randfall, und es ist wahrscheinlich eine Überlegung wert, ob er überhaupt im Rahmen der TS-Ziele liegt ... aber so oder so könnte er wahrscheinlich separat gelöst werden, da mein Vorschlag hier mit dem aktuellen Verhalten von TypeScript übereinstimmt.

Ich habe wirklich das Gefühl, dass ich nur alle paar Seiten mit Kommentaren einfügen und erneut einen Link zu #2521 einfügen muss.

Es erscheint zu komplex, die Typsyntax sowohl für "Der Indexlesetyp von" als auch "Der Indexschreibtyp von" hinzuzufügen. Die Gedanken?

Nö! Und immerhin (klickt Link...) 116 andere hier stimmen mir zu. Ich würde mich so freuen , wenn ich zumindest die Möglichkeit hätte, Lese- und Schreibvorgänge anders zu schreiben. Dies ist nur ein weiterer großartiger Anwendungsfall für diese Funktion.

Ich wäre _so glücklich_, zumindest die Möglichkeit zu haben, Lese- und Schreibvorgänge anders zu schreiben. Dies ist nur _noch ein weiterer_ großartiger Anwendungsfall für diese Funktion.

Ich glaube, die Absicht war, zu fragen, ob es eine Möglichkeit geben sollte, auf die Lese- und Schreibtypen zu verweisen, zB sollte es so etwas wie Array<string>[number]= , um auf den Schreibtyp zu verweisen?

Ich wäre mit der Lösung von #2521 und diesem Problem zufrieden, wenn zwei Dinge passieren:

  • Geben Sie zunächst Getter und Setter unterschiedlich ein, wie in #2521 . beschrieben
  • Erstellen Sie dann wie in dieser Ausgabe beschrieben ein Flag, sodass der "Schreibtyp" von Array<string>[number] string während der "Lesetyp" string | undefined .

Ich gehe (fälschlicherweise?) davon aus, dass ersteres die Grundlage für letzteres legen würde, siehe https://github.com/microsoft/TypeScript/issues/13778#issuecomment -630770947. Ich habe keinen besonderen Bedarf an einer expliziten Syntax, um den "Schreibtyp" selbst zu definieren.

@RyanCavanaugh kurze Metafrage : meinen eher spezifischen Vorschlag oben als separates Thema zu haben, um seine Stärken/Schwächen/Kosten klarer zu diskutieren und zu verfolgen (insbesondere, da die Leistungskosten nicht trivial sind)? Oder würde dies nur das Rauschen erhöhen?

@RyanCavanaugh

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

als Umweg, um const t: string schreiben.

Ich denke, das ist die gleiche Argumentation für Tupel?

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

Tupel kennen jedoch Grenzen und geben undefined für spezifischere Zahlentypen korrekt zurück:

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 

warum sollte sich number anders verhalten?

Es scheint, als hätten Sie die Mechanismen dafür: https://github.com/microsoft/TypeScript/issues/38779

Dies würde meine Bedürfnisse erfüllen, ohne eine zusätzliche Flagge hinzuzufügen.

Falls es jemandem hilft, hier ist ein ESLint-Plugin, das unsicheren Array-Zugriff und die Destrukturierung von Arrays/Objekten markiert. Es kennzeichnet keine sicheren Verwendungen wie Tupel und (Nicht-Datensatz-)Objekte.

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

Cheers, aber ich hoffe, es ist klar, dass dies auf Sprachebene noch hinzugefügt werden muss, da es sich ausschließlich um die Typprüfung handelt und einige Projekte keinen Linter oder die richtigen Regeln haben.

@RyanCavanaugh

Wir haben auch getestet, wie dieses Feature in der Praxis aussieht und ich kann euch sagen, dass es nicht schön ist

Nur neugierig, können Sie uns sagen, woran der unschöne Teil liegt? Vielleicht können wir das klären, wenn Sie mehr darüber sagen können.

Dies sollte keine Option sein, es sollte die Standardeinstellung sein .

Das heißt, wenn das Typsystem von TypeScript zur Kompilierzeit die Typen beschreiben soll, die JavaScript-Werte zur Laufzeit annehmen können:

  • Die Indizierung eines Array kann natürlich undefined in JavaScript zurückgeben (Index außerhalb der Grenzen oder spärliches Array), also wäre die korrekte Lesesignatur T | undefined .
  • Einen Index in Array zu undefined "verursachen" ist auch über das Schlüsselwort delete .

Wie TypeScript 4 verhindert

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

es gibt gute Gründe, auch a[1] = undefined .
Dies legt nahe, dass Array<T>[number] tatsächlich für Lese- und Schreibvorgänge unterschiedlich sein sollte.

Es mag "komplex" sein, aber es würde es ermöglichen, die Laufzeitmöglichkeiten in JavaScript genauer zu modellieren;
Wofür wurde TypeScript _gemacht_, richtig?

Es hat keinen Sinn, die Idee zu diskutieren, | undefined zum Standardverhalten zurückzugeben – sie werden niemals eine Version von Typescript veröffentlichen, die aufgrund eines Compiler-Updates nur Millionen von Zeilen an Legacy-Code explodiert. Eine solche Veränderung wäre die bahnbrechendste Veränderung in jedem Projekt, das ich je verfolgt habe.

Dem Rest des Beitrags stimme ich allerdings zu.

Es hat keinen Sinn, die Idee zu diskutieren, | undefined zum Standardverhalten zurückzugeben – sie werden _nie_ eine Version von Typescript veröffentlichen, die aufgrund eines Compiler-Updates nur Millionen von Zeilen an Legacy-Code explodiert. Eine solche Veränderung wäre die bahnbrechendste Veränderung in jedem Projekt, das ich je verfolgt habe.

Dem Rest des Beitrags stimme ich allerdings zu.

Nicht, wenn es eine Konfigurationsoption ist.

Nicht, wenn es eine Konfigurationsoption ist.

Das ist fair, aber zumindest würde ich eine lange "Vorlaufphase" erwarten, in der es standardmäßig deaktiviert ist, was den Leuten viel Zeit gibt (6 Monate? ein Jahr? mehr?) -Ursprünglich. Fügen Sie zum Beispiel das Flag in v4.0 hinzu, setzen Sie es in v5.0 standardmäßig auf on, so etwas.

Aber das ist weit in die Zukunft, an dieser Stelle, weil wir noch nicht haben Buy-in , dass die Voraussetzung Funktion (verschiedene Typen für Setter / Getter) ist lohnt sich. Ich stehe also zu der Aussage, dass es keinen Sinn macht, über das Standardverhalten lange, lange Zeit zu diskutieren.

Es hat keinen Sinn, die Idee zu diskutieren, | undefined zum Standardverhalten zurückzugeben – sie werden _nie_ eine Version von Typescript veröffentlichen, die aufgrund eines Compiler-Updates nur Millionen von Zeilen an Legacy-Code explodiert. Eine solche Veränderung wäre die bahnbrechendste Veränderung in jedem Projekt, das ich je verfolgt habe.

Dem Rest des Beitrags stimme ich allerdings zu.

Zu Ihrem Punkt @thw0rted habe ich hier einen Kompromiss vorgeschlagen: https://github.com/microsoft/TypeScript/issues/38779

Es ermöglicht diese Funktionalität in einem Feature, das viel später eingeführt wurde, das immer noch nicht so weit verbreitet ist, und es scheint, als wäre es "fast" schon da und bedarf nur kleiner Änderungen.

Für "strict" Konsistenz sollte etwas von Array<T> als "a list of type T that is infinitly long with no gaps" Immer wenn dies nicht erwünscht ist, sollten Sie Tupel verwenden.

Ja, dies hat Nachteile, aber ich habe wieder das Gefühl, dass es ein fairer "Kompromiss" zwischen dieser Funktion ist, die nie implementiert wird und genau so implementiert wird, wie wir es hier fordern.

Wenn Sie zustimmen, stimmen Sie bitte zu

Ich bin nicht der Meinung, dass dies eine ganz neue Option/Konfiguration erfordert.

Ich denke nicht, dass es Standardverhalten sein sollte, wenn die strikte Option deaktiviert ist, aber wenn ich die strikte Option BE STRICT aktiviere und sie auch bei älteren Projekten erzwinge. Das Aktivieren von strikt ist das, was die Leute für die strengsten/genauesten Eingaben entscheiden. Wenn ich eine strikte Option aktiviere, möchte ich Vertrauen in meinen Code haben und das ist das Beste an TS. Aber diese ungenauen Eingaben, selbst wenn strikt aktiviert ist, bereiten nur Kopfschmerzen.

Um die Lesbarkeit dieses Threads zu gewährleisten, habe ich eine Reihe von tangentialen Kommentaren ausgeblendet, die die Konversation nicht sinnvoll ergänzten. Im Allgemeinen können Sie davon ausgehen, dass in den vorangegangenen 212 Kommentaren bereits Kommentare der folgenden Art gemacht wurden und nicht wiederholt werden müssen:

  • TypeScript sollte zum ersten Mal eine unpraktisch große Breaking Change im Prüfverhalten einführen - nein, das haben wir noch nicht gemacht und beabsichtigen auch nicht, dies in Zukunft zu tun, bitte beschäftigen Sie sich mehr mit den langfristigen Designzielen des Projekts, bevor Sie hierher kommen, um sich für beispiellose Veränderungen einzusetzen
  • Argumente basierten auf der Idee, dass niemand die Tatsache in Betracht gezogen hat, dass ein Konfigurations-Flag existiert - wir sind uns bewusst, dass Dinge hinter einer Flagge nicht standardmäßig aktiviert sein müssen
  • Was soll's, mach es einfach schon! - nun, wo du es so ausdrückst, bin ich überzeugt 😕

Es gibt sehr reale praktische und technische Herausforderungen, die hier gelöst werden müssen, und es ist enttäuschend, dass wir in diesem Thread keine sinnvolle Arbeit leisten können, weil er voller Leute ist, die vorbeischauen, um zu suggerieren, dass alles hinter einer Flagge keine wirklichen Konsequenzen hat oder dass jeder Fehler, der dies kann be ts-ignore 'd ist keine Upgrade-Belastung.

@RyanCavanaugh

danke für deine Zusammenfassung.

Bitte beschäftigen Sie sich mehr mit den langfristigen Designzielen des Projekts, bevor Sie hierher kommen, um sich für beispiellose Veränderungen einzusetzen

Ich habe meinen Kommentar gelöscht und kann auf Wunsch auch diesen Kommentar löschen. Aber wie versteht man das erste Ziel des Projekts:

1. Konstrukte, bei denen es sich wahrscheinlich um Fehler handelt, statisch identifizieren.

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

Ich verstehe Ihren zitierten Kommentar nicht, da ein ungeprüfter Indexzugriff wahrscheinlich zu einem Laufzeitfehler führen kann. Wenn Sie in JavaScript so etwas erwarten, bekommen Sie in TypeScript gefährliches und falsches Vertrauen. Falsche Typen sind schlimmer als any .

naja jetzt wo du es so sagst bin ich überzeugt

Natürlich sind Sie einer der Core-Maintainer und danken Ihnen und dem Team und den Mitwirkenden für ts. Aber es geht nicht mehr nur um dich. Vergleichen Sie Upvotes: 365 mit Downvotes: 6! Nur 6! Es zeigt einen großen Bedarf an Typsicherheit.

Aber sprechen wir über Lösungen. Gibt es eine Lösung, die das Team umsetzen würde oder sich vorstellen kann?

Können Sie bitte etwas genauer erläutern, was in diesem Fall mit ts-ignore nicht stimmt? Ich meine, es kann mit Tools wie Codemod automatisiert werden und ändert nicht das Verhalten des aktuellen Codes. Natürlich ist dies kein schöner Workaround, aber es ist eine Möglichkeit, diese Änderung ohne Flags einzuführen.

Was halten Sie von der automatischen Veröffentlichung einer gepatchten Version von ts, zum Beispiel 4.0.0-breaking ? Es führt einige (viel?) Arbeiten zu Konflikten ein, aber es ermöglicht jedem, Änderungen zu testen und die Codebasis vorzubereiten (nicht nur für diese Feature-Anfrage). Dies kann für einen begrenzten Zeitraum erfolgen, z. B. 3-6 Monate. Wir wären die Ersten, die diese Version verwenden werden.

nicht mehr. Vergleichen Sie Upvotes: 365 mit Downvotes: 6! Nur 6! Es zeigt einen großen Bedarf an Typsicherheit.
- @Bessonov

Haha.
Nicht, dass ich dem ganzen Beitrag von @RyanCavanaugh zustimme.. aber... komm schon..
gibt es was für millionen? TS-Benutzer da draußen?!? 365 wollen diese Funktion genug, um in diesem Thread zu kommentieren und zu stimmen...

Es gibt keine Benachrichtigung von github, aber für alle, die versuchen würden, mit undefiniertem Index aus dieser Ausgabe zu spielen, werfen Sie einen Blick auf den PR-Entwurf über meinem Kommentar.

@RyanCavanaugh vielen Dank, dass Sie uns die Möglichkeit geben, damit zu spielen. Ich habe es gegen eine ziemlich kleine Codebasis (~ 105 ts (x) Dateien) ausgeführt und ein wenig damit gespielt. Ich habe kein wichtiges Problem gefunden. Eine Zeile, die geändert wurde von:

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

zu:

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

Ich werde es nächste Woche bei einem mittleren Projekt ausprobieren.

@lonewarrior556 es ist 60 Mal mehr und es ist auf der ersten Seite, wenn Sie nach Upvotes sortieren :)

@Bessonov Ich denke, Sie sollten es in der Codebasis des Typescript-Compilers ausprobieren. Es wird wahrscheinlich aufgrund der nicht trivialen Verwendung von for-Schleifen zu massiven

Sie könnten sogar Flags zum An- und Abmelden haben, um Abwärtskompatibilität zu gewährleisten.

Jedes hinzugefügte Flag ist eine weitere Konfigurationsmatrix, die getestet und unterstützt werden muss. Aus diesem Grund möchte TypeScript Flags auf ein Minimum beschränken.

@MatthiasKunnen Wenn dies ein kleines Edge-Case-Feature wäre, würde ich zustimmen, dass dies ein gangbarer Grund ist, dafür kein Flag hinzuzufügen. Aber direkter Array-Zugriff taucht ziemlich häufig im Code auf und ist sowohl ein eklatantes Loch im Typsystem als auch eine bahnbrechende Änderung, die behoben werden muss, daher denke ich, dass ein Flag gerechtfertigt wäre.

Eine Flagge ist hier der falsche Ansatz, auch wegen der kombinatorischen
Problem. Wir sollten das einfach TypeScript v5 nennen... so ein Ansatz
ändert die Anzahl der testbaren Kombinationen von 2^N auf nur N...

Am Sa, 15. August 2020 um 15:56 Uhr, Michael Burkman [email protected]
schrieb:

@MatthiasKunnen https://github.com/MatthiasKunnen Wenn das einige wären
kleines Edge-Case-Feature, dann würde ich zustimmen, dass dies ein praktikabler Grund ist, es nicht zu tun
Füge dafür eine Flagge hinzu. Aber direkter Array-Zugriff wird im Code ziemlich angezeigt
häufig und ist sowohl ein eklatantes Loch im Typensystem als auch ein
Breaking Change zu beheben, daher denke ich, dass eine Flagge gerechtfertigt wäre.


Sie erhalten dies, weil Sie diesen Thread abonniert haben.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/microsoft/TypeScript/issues/13778#issuecomment-674408067 ,
oder abmelden
https://github.com/notifications/unsubscribe-auth/AAADY5AXEY65S5HDGNGIPZDSA2O3FANCNFSM4C6KEKAA
.

Hatte gerade ein unerwarteter undefinierter Fehler meine Anwendung für Hunderte von Benutzern zum Absturz gebracht, ein Fehler, der zur Build-Zeit abgefangen worden wäre, wenn diese Typprüfung vorhanden wäre. TypeScript war für uns eine erstaunliche Möglichkeit, zuverlässigere Software bereitzustellen, aber sein großer Nutzen wird durch diese einzige Unterlassung untergraben.

Wie wäre es, wenn die nächsten 10 Leute, die hier kommentieren wollen, stattdessen einfach den PR-Entwurf #39560 testen?

Dies ist sehr ärgerlich, wenn Sie die Regel @typescript-eslint/no-unnecessary-condition ESLint aktivieren möchten, da sie sich dann über alle Instanzen von . beschwert

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

Es hält dies für eine unnötige Bedingung (weil Typescript sagt, dass es so ist!), aber das ist es nicht. Ich möchte nicht wirklich // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition meiner gesamten Codebasis hinzufügen müssen.

Wenn es für faule Javascript-Programmierer zu viel Arbeit ist, dies richtig zu reparieren, könnten wir vielleicht eine alternative Array-Zugriffssyntax haben, die undefined hinzufügen würde, z

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

(zufälliger Syntaxvorschlag; Alternativen willkommen)

@Timmmm bitte lesen Sie den Kommentar direkt über dem, den Sie gemacht haben. Es gibt einen Build, den Sie ausprobieren können, der eine Version dieses Vorschlags implementiert. Sie können uns dort mitteilen, ob Ihr eslint Problem dadurch behoben wird.

@the0rted Ich habe diesen Kommentar gelesen. Es implementiert keine Version meines Vorschlags. Vielleicht solltest du es einfach nochmal lesen.

Tut mir leid, als ich "diesen" Vorschlag sagte, meinte ich die Fähigkeit im OP, dh die Behandlung von array_of_T[i] als T | undefined . Ich sehe, dass Sie nach einer Syntax fragen, um einen bestimmten Indexoperator als "vielleicht undefiniert" zu markieren, aber wenn Sie die Implementierung in Ryans PR verwenden, würden Sie das nicht brauchen, da alle Indexoperatoren "vielleicht undefiniert" wären. . Würde das nicht Ihren Bedürfnissen entsprechen?

Ja, würde es - wenn / wenn es landet, werde ich es auf jeden Fall verwenden. Aber ich hatte den Eindruck aus dieser Diskussion, dass viele Leute dagegen waren, weil es ein zusätzliches Flag hinzufügt und Code komplizierter macht (das einfache Beispiel für eine for-Schleife).

Also wollte ich einen alternativen Vorschlag machen, aber anscheinend hassen die Leute ihn. :-/

Alle gut gemeinten Eingaben werden geschätzt, versteh es nicht falsch @ Timmmm. Ich denke, die Downvotes vermitteln nur, dass dies an dieser Stelle in diesem Thread bereits ausgetreten ist.

Für mich ist das Hinzufügen eines Flags, das die Typsicherheit auf breiter Front verbessert, viel kostengünstiger als die Einführung einer neuen Opt-in-Syntax.

Ich habe es oben nicht erwähnt, aber eine Syntax für einmalige Überschreibungen existiert bereits: if ((some_array[i] as MyType | undefined) === undefined) . Es ist nicht so knapp wie eine neue Kurzschrift, aber hoffentlich kein Konstrukt, das Sie sehr oft verwenden müssen.

_Ursprünglich gepostet von @osyrisrblx in https://github.com/microsoft/TypeScript/issues/40435#issuecomment -690017567_

Obwohl dies eine viel sicherere Option zu sein scheint, wäre es schön, dieses Verhalten mit einer separaten Syntax für den seltenen Fall zu deaktivieren, in dem Sie 100% sicher sind, dass es immer definiert ist.

Vielleicht könnte !: immer definiert behaupten?

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

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

Dieser Vorschlag beschreibt das Problem falsch. Das Problem liegt nicht im Verständnis des Typs durch TypeScript selbst. Das Problem kommt vom _Zugriff_ auf die Daten, wo bereits früher in diesem Thread eine neue Syntax vorgeschlagen wurde.

Sie können dies in der TypeScript 4.1-Beta ausprobieren, die bald herauskommt ™ (oder heute Abend, wenn Sie es jetzt brauchen! 🙂)

Tut mir leid, wenn dies schon einmal angeboten wurde, aber könnte {[index: string]?: number} // -> number | undefined unterstützt werden? Es stimmt mit der Syntax der optionalen Eigenschaften der Schnittstelle überein - standardmäßig erforderlich, möglicherweise nicht mit "?" definiert.

Die Compiler-Option in 4.1 ist cool, aber eine genauere Kontrolle wäre auch gut.

Wenn Sie es in Ihrem Repo ausprobieren möchten (ich habe ein bisschen gebraucht, um es herauszufinden):

  1. Installiere typescript@next yarn (add|upgrade) typescript@next
  2. Flag hinzufügen (für mich in tsconfig.json) "noUncheckedIndexedAccess": true

Beim Aktivieren dieser Regel für mein Projekt bin ich auf diesen interessanten Fehler gestoßen:

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

In diesem Fall habe ich nicht erwartet, dass myRecord[key] den Typ MyRecord[Key] | undefined , da key auf keyof MyRecord .

Update : eingereichtes Problem https://github.com/microsoft/TypeScript/issues/40666

Das ist aber wahrscheinlich ein Fehler/Versehen!

Beim Aktivieren dieser Regel für mein Projekt bin ich auf diesen interessanten Fehler gestoßen:

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

In diesem Fall habe ich nicht erwartet, dass myRecord[key] den Typ MyRecord[Key] | undefined , da key auf keyof MyRecord .

Ich würde sagen, das ist ein Bug. Grundsätzlich, wenn keyof Type nur String/Zahl-Literaltypen enthält (im Gegensatz zu string oder number ), dann Type[Key] wobei Key extends keyof Type sollte nicht undefined , würde ich denken.

Querverknüpfung zu #13195, die auch Unterschiede und Ähnlichkeiten zwischen "hier gibt es keine Eigenschaft" und "hier gibt es eine Eigenschaft, aber undefined " betrachtet.

Verfolgen Sie hier einige Probleme im Zusammenhang mit Designbeschränkungen

  • #41612

Zu Ihrer Information: Dank dieses Flags habe ich zwei Fehler in meiner Codebasis gefunden, also vielen Dank!
Es ist ein bisschen ärgerlich, dass das Überprüfen von arr.length > 0 nicht ausreicht, um arr[0] zu schützen, aber das ist eine kleine Unannehmlichkeit (ich kann den Check umschreiben, um tsc glücklich zu machen) im Vergleich zu der zusätzlichen Sicherheit, IMO.

@rubenlg Ich stimme arr.length . Um nur das erste Element zu überprüfen, habe ich unseren Code umgeschrieben wie

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

Aber es gibt ein paar Orte, an denen wir if (arr.length > 2) oder mehr machen, die etwas umständlich sind. Ich glaube jedoch nicht, dass das Überprüfen von .length sowieso völlig typsicher wäre, da Sie es einfach ändern können:

const a: number[] = [];

a.length = 1;

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

Druckt undefined .

Ich glaube jedoch nicht, dass das Überprüfen von .length sowieso völlig typsicher wäre, da Sie es einfach ändern können:

const a: number[] = [];

a.length = 1;

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

Druckt undefined .

Das wäre im Wesentlichen ein spärliches Array, das für solche Dinge außerhalb des Anwendungsbereichs liegt. Es gibt immer nicht standardmäßige oder hinterhältige Dinge, die getan werden können, um die Typprüfung zu umgehen.

Es gibt immer eine Möglichkeit, die vorherige Annahme zu entkräften, und das ist das Problem. Jeden möglichen Ausführungspfad zu analysieren wäre viel zu kostspielig.

const a: number[] = [1]
if (a.length > 0) {
    a.pop();
    console.log(a[0])
}
War diese Seite hilfreich?
0 / 5 - 0 Bewertungen