Haftungsausschluss: Dieses Problem hat nicht den Zweck zu beweisen, dass Flow besser oder schlechter als TypeScript ist. Ich möchte nicht die erstaunlichen Arbeiten beider Teams kritisieren, sondern die Unterschiede im Flow- und TypeScript- Typsystem auflisten und versuchen, zu bewerten, welche Funktion vorhanden ist könnte TypeScript verbessern.
Ich werde auch nicht über fehlende Funktionen in Flow sprechen, da der Zweck darin besteht, TypeScript zu verbessern.
Schließlich geht es in diesem Thema nur um das Typsystem und nicht um unterstützte es6 / es7-Funktionen.
mixed
und any
Aus dem Flow-Dokument:
- gemischt: der "Supertyp" aller Typen. Jeder Typ kann in eine Mischung fließen.
- any: der "dynamische" Typ. Jeder Typ kann in jeden fließen und umgekehrt
Grundsätzlich bedeutet dies, dass mit flow any
das Äquivalent von TypeScript any
und mixed
das Äquivalent von TypeScript {}
.
Object
mit FlowAus dem Flow-Dokument:
Verwenden Sie gemischt, um einen Ort zu kommentieren, der alles aufnehmen kann, aber verwenden Sie stattdessen kein Objekt! Es ist verwirrend, alles als Objekt anzusehen, und wenn Sie zufällig "irgendein Objekt" meinen, gibt es eine bessere Möglichkeit, dies anzugeben, genauso wie es eine Möglichkeit gibt, "jede Funktion" anzugeben.
Mit TypeScript entspricht Object
{}
und akzeptiert jeden Typ, mit Flow Object
entspricht {}
, unterscheidet sich jedoch von mixed
, es werden nur Objekte akzeptiert (und keine anderen primitiven Typen wie string
, number
, boolean
oder function
).
function logObjectKeys(object: Object): void {
Object.keys(object).forEach(function (key) {
console.log(key);
});
}
logObjectKeys({ foo: 'bar' }); // valid with TypeScript and Flow
logObjectKeys(3); // valid with TypeScript, Error with flow
In diesem Beispiel ist der Parameter logObjectKeys
mit dem Typ Object
, für TypeScript, das {}
und akzeptiert daher jeden Typ, wie z. B. number
im Fall des zweiten Aufrufs logObjectKeys(3)
.
Mit Flow sind andere primitive Typen nicht mit Object
kompatibel. Daher meldet der Typprüfer beim zweiten Aufruf logObjectKeys(3)
Fehler: _number ist nicht kompatibel mit Object_.
Aus dem Flow-Dokument:
In JavaScript wird null implizit in alle primitiven Typen konvertiert. Es ist auch ein gültiger Bewohner eines beliebigen Objekttyps.
Im Gegensatz dazu betrachtet Flow null als einen bestimmten Wert, der nicht Teil eines anderen Typs ist.
Siehe Abschnitt Flow doc
Da das Ablaufdokument ziemlich vollständig ist, werde ich diese Funktion nicht im Detail beschreiben. Beachten Sie jedoch, dass der Entwickler gezwungen ist, alle Variablen zu initialisieren oder als nullbar zu markieren. Beispiele:
var test: string; // error undefined is not compatible with `string`
var test: ?string;
function getLength() {
return test.length // error Property length cannot be initialized possibly null or undefined value
}
Wie bei der TypeScript-Typschutzfunktion sollte Flow jedoch die Nicht-Null-Prüfung verstehen:
var test: ?string;
function getLength() {
if (test == null) {
return 0;
} else {
return test.length; // no error
}
}
function getLength2() {
if (test == null) {
test = '';
}
return test.length; // no error
}
Siehe Abschnitt Flow doc
Siehe Correspondin TypeScript-Problem Nr. 1256
Wie TypeScript Flow Support Union-Typen unterstützt es auch eine neue Art der Kombination von Typen: Schnittpunkttypen.
Bei Objekten sind Schnittpunkttypen wie das Deklarieren von Mixins:
type A = { foo: string; };
type B = { bar : string; };
type AB = A & B;
AB hat für Typ { foo: string; bar : string;}
;
Für Funktionen entspricht dies der Deklaration von Überlast:
type A = () => void & (t: string) => void
var func : A;
ist äquivalent zu :
interface A {
(): void;
(t: string): void;
}
var func: A
Betrachten Sie das folgende TypeScript-Beispiel:
declare function promisify<A,B>(func: (a: A) => B): (a: A) => Promise<B>;
declare function identity<A>(a: A): A;
var promisifiedIdentity = promisify(identity);
Mit TypeScript hat promisifiedIdentity
für den Typ:
(a: {}) => Promise<{}>`.
Mit Flow haben promisifiedIdentity
für Typ:
<A>(a: A) => Promise<A>
Flow versucht im Allgemeinen, mehr Typ als TypeScript abzuleiten.
Schauen wir uns dieses Beispiel an:
function logLength(obj) {
console.log(obj.length);
}
logLength({length: 'hello'});
logLength([]);
logLength("hey");
logLength(3);
Bei TypeScript werden keine Fehler gemeldet. Bei Flow führt der letzte Aufruf von logLength
zu einem Fehler, da number
keine length
-Eigenschaft hat.
Bei Flow ändert sich der Typ dieser Variablen mit der Verwendung dieser Variablen, sofern Sie Ihre Variable nicht ausdrücklich eingeben:
var x = "5"; // x is inferred as string
console.log(x.length); // ok x is a string and so has a length property
x = 5; // Inferred type is updated to `number`
x *= 5; // valid since x is now a number
In diesem Beispiel hat x anfangs den Typ string
, aber wenn es einer Nummer zugewiesen wurde, wurde der Typ in number
geändert.
Mit Typoskript würde die Zuweisung x = 5
zu einem Fehler führen, da x
zuvor string
zugewiesen wurde und sich sein Typ nicht ändern kann.
Ein weiterer Unterschied besteht darin, dass Flow die Typinferenz rückwärts propagiert, um den abgeleiteten Typ zu einer Typvereinigung zu erweitern. Dieses Beispiel stammt von Facebook / Flow # 67 (Kommentar)
class A { x: string; }
class B extends A { y: number; }
class C extends A { z: number; }
function foo() {
var a = new B();
if (true) a = new C(); // TypeScript reports an error, because a's type is already too narrow
a.x; // Flow reports no error, because a's type is correctly inferred to be B | C
}
("richtig" stammt aus dem Originalbeitrag.)
Da die erfasste Fluss dass der a
variable haben könnte B
Typen oder C
je nach Typ einer bedingten Anweisung wird nun gefolgert B | C
, und so die Die Anweisung a.x
führt nicht zu einem Fehler, da beide Typen eine x
-Eigenschaft haben, wenn wir versucht hätten, auf die z
-Eigenschaft zuzugreifen, und ein Fehler aufgetreten wäre.
Dies bedeutet, dass auch Folgendes kompiliert wird.
var x = "5"; // x is inferred as string
if ( true) { x = 5; } // Inferred type is updated to string | number
x.toString(); // Compiles
x += 5; // Compiles. Addition is defined for both string and number after all, although the result is very different
mixed
und any
aktualisiert, da mixed
dem Äquivalent von {}
beispielsweise keine Notwendigkeit besteht.Object
hinzugefügt.Fühlen Sie sich frei zu benachrichtigen, wenn ich etwas vergessen habe Ich werde versuchen, das Problem zu aktualisieren.
Dies ist interessant und ein guter Ausgangspunkt für weitere Diskussionen. Stört es Sie, wenn ich aus Gründen der Klarheit einige Änderungen am Originalbeitrag vornehme?
Unerwartete Dinge in Flow (wird diesen Kommentar aktualisieren, wenn ich ihn genauer untersuche)
Inferenz des Argumenttyps für ungerade Funktion:
/** Inference of argument typing doesn't seem
to continue structurally? **/
function fn1(x) { return x * 4; }
fn1('hi'); // Error, expected
fn1(42); // OK
function fn2(x) { return x.length * 4; }
fn2('hi'); // OK
fn2({length: 3}); // OK
fn2({length: 'foo'}); // No error (??)
fn2(42); // Causes error to be reported at function definition, not call (??)
Keine Typinferenz aus Objektliteralen:
var a = { x: 4, y: 2 };
// No error (??)
if(a.z.w) { }
Dies ist interessant und ein guter Ausgangspunkt für weitere Diskussionen. Stört es Sie, wenn ich aus Gründen der Klarheit einige Änderungen am Originalbeitrag vornehme?
Fühlen Sie sich frei Wie ich bereits sagte, besteht der Zweck darin, ein Flow-Typ-System zu investieren, um festzustellen, ob einige Funktionen in TypeScript 1 passen könnten.
@ RyanCavanaugh Ich denke das letzte Beispiel:
var a = { x: 4, y: 2 };
// No error (??)
if(a.z.w) { }
Ist ein Fehler mit ihrem Null-Check-Algorithmus verbunden, werde ich ihn melden.
Ist
type A = () => void & (t: string) => void
var func : A;
Gleichwertig
Declare A : () => void | (t: string) => void
var func : A;
Oder könnte es sein?
@ Davidhanson90 nicht wirklich:
declare var func: ((t: number) => void) | ((t: string) => void)
func(3); //error
func('hello'); //error
In diesem Beispiel kann flow nicht wissen, welcher Typ im Unionstyp func
ist, sodass in beiden Fällen ein Fehler gemeldet wird
declare var func: ((t: number) => void) & ((t: string) => void)
func(3); //no error
func('hello'); //no error
func hat beide Typen, sodass beide Aufrufe gültig sind.
Gibt es einen beobachtbaren Unterschied zwischen {}
in TypeScript und mixed
in Flow?
@ RyanCavanaugh Ich weiß es nicht wirklich, nachdem ich gedacht habe, dass es ziemlich dasselbe ist,
Dies ist falsch.mixed
hat keine Eigenschaften, nicht einmal die Eigenschaften, die von Object.prototype geerbt wurden, die {}
hat (# 1108)
Ein weiterer Unterschied besteht darin, dass Flow die Typinferenz rückwärts propagiert, um den abgeleiteten Typ zu einer Typvereinigung zu erweitern. Dieses Beispiel stammt von https://github.com/facebook/flow/issues/67#issuecomment -64221511
class A { x: string; }
class B extends A { y: number; }
class C extends A { z: number; }
function foo() {
var a = new B();
if (true) a = new C(); // TypeScript reports an error, because a's type is already too narrow
a.x; // Flow reports no error, because a's type is correctly inferred to be B | C
}
("richtig" stammt aus dem Originalbeitrag.)
Dies bedeutet, dass auch Folgendes kompiliert wird.
var x = "5"; // x is inferred as string
if ( true) { x = 5; } // Inferred type is updated to string | number
x.toString(); // Compiles
x += 5; // Compiles. Addition is defined for both string and number after all, although the result is very different
Bearbeiten: Das zweite Snippet wurde getestet und es wird wirklich kompiliert.
Bearbeiten 2: Wie von @fdecampredon unten ausgeführt, ist das if (true) { }
um die zweite Zuweisung erforderlich, damit Flow den Typ als string | number
ableiten kann. Ohne die if (true)
wird stattdessen auf number
.
Gefällt dir dieses Verhalten? Wir sind diesen Weg gegangen, als wir über Gewerkschaftstypen gesprochen haben, und der Wert ist zweifelhaft. Nur weil das Typsystem jetzt Typen mit mehreren möglichen Zuständen modellieren kann, heißt das nicht, dass es wünschenswert ist, diese überall zu verwenden. Angeblich haben Sie sich für die Verwendung einer Sprache mit einer statischen Typprüfung entschieden, weil Sie Compilerfehler wünschen, wenn Sie Fehler machen, nicht nur, weil Sie gerne Typanmerkungen schreiben;) Das heißt, die meisten Sprachen geben in einem Beispiel wie diesem einen Fehler an (insbesondere die zweite) nicht aus Mangel an einer Möglichkeit, den Typraum zu modellieren, sondern weil sie tatsächlich glauben, dass dies ein Codierungsfehler ist (aus ähnlichen Gründen verzichten viele darauf, viele implizite Cast- / Konvertierungsoperationen zu unterstützen).
Nach der gleichen Logik würde ich dieses Verhalten erwarten:
declare function foo<T>(x:T, y: T): T;
var r = foo(1, "a"); // no problem, T is just string|number
aber ich will dieses Verhalten wirklich nicht.
@danquirk Ich stimme Ihnen zu, dass das automatische Ableiten des Vereinigungstyps anstelle des
Aber ich denke, das kommt von der Flow-Philosophie, mehr als eine echte Sprache. Das Flow-Team versucht, einfach eine Typprüfung zu erstellen. Das ultimative Ziel ist es, "sichereren" Code ohne Typanmerkungen zu erstellen. Dies führte dazu, weniger streng zu sein.
Die genaue Strenge ist angesichts der Auswirkungen dieser Art von Verhalten sogar umstritten. Oft wird nur ein Fehler verschoben (oder vollständig ausgeblendet). Unsere alten Typinferenzregeln für Typargumente spiegelten eine ähnliche Philosophie wider. Im Zweifelsfall haben wir {} für einen Typparameter abgeleitet, anstatt ihn zu einem Fehler zu machen. Dies bedeutete, dass Sie einige doofe Dinge tun und dennoch einige minimale Verhaltensweisen sicher für das Ergebnis ausführen konnten (nämlich Dinge wie toString
). Das Grundprinzip ist, dass einige Leute in JS doofe Dinge tun und wir sollten versuchen zuzulassen, was wir können. In der Praxis waren die meisten Schlussfolgerungen zu {} jedoch nur Fehler, und Sie mussten warten, bis Sie zum ersten Mal eine Variable vom Typ T
abgetastet haben, um
declare function foo(arg: number);
var x = "5";
x = 5;
foo(x); // error
Was ist der Fehler hier? Übergibt es wirklich x
an foo
? Oder wurde x
ein Wert eines völlig anderen Typs zugewiesen, als er initialisiert wurde? Wie oft machen Menschen diese Art der Neuinitialisierung wirklich absichtlich, anstatt versehentlich auf etwas zu stampfen? Wenn Sie auf einen Unionstyp für x
können Sie auf jeden Fall wirklich sagen, dass das Typsystem insgesamt weniger streng war, wenn es dennoch zu einem (schlimmeren) Fehler führte? Diese Art der Folgerung ist nur dann weniger streng, wenn Sie mit dem resultierenden Typ, der im Allgemeinen ziemlich selten ist, niemals etwas besonders Bedeutendes tun.
Wenn null
und undefined
wahrscheinlich auf die gleiche Weise einem Typ ausgeblendet werden können, führt eine Variable, die mit einem Typ eingegeben wurde und einen null
verbirgt, meistens zu einem Fehler zur Laufzeit.
Ein nicht unerheblicher Teil des Marketings von Flow basiert auf der Tatsache, dass der Typechecker an Stellen, an denen TS auf any
schließen würde, mehr Sinn für Code macht. Die Philosophie lautet, dass Sie keine Anmerkungen hinzufügen müssen, damit der Compiler auf Typen schließen kann. Aus diesem Grund wird das Inferenzrad auf eine viel freizügigere Einstellung eingestellt als das von TypeScript.
Es kommt darauf an, ob jemand die Erwartung hat, dass var x = new B(); x = new C();
(wobei B und C beide von A abgeleitet sind) kompiliert werden sollte oder nicht, und wenn ja, wie sollte daraus geschlossen werden?
B | C
.TS macht derzeit (1) und Flow macht (3). Ich bevorzuge (1) und (2) viel mehr als (3).
Ich wollte der Originalausgabe @ Arnavion- Beispiele hinzufügen, aber nachdem ich ein bisschen gespielt hatte, wurde mir klar, dass die Dinge seltsamer waren als das, was wir verstanden haben.
In diesem Beispiel:
var x = "5"; // x is inferred as string
x = 5; // x is infered as number now
x.toString(); // Compiles, since number has a toString method
x += 5; // Compiles since x is a number
console.log(x.length) // error x is a number
Jetzt :
var x = '';
if (true) {
x = 5;
}
Nach diesem Beispiel ist x string | number
Und wenn ich das tue:
1. var x = '';
2. if (true) {
3. x = 5;
4. }
5. x*=5;
In Zeile 1 wurde die Fehlermeldung angezeigt: myFile.js line 1 string this type is incompatible with myFile.js line 5 number
Ich muss hier noch die Logik herausfinden ...
Es gibt auch einen interessanten Punkt über den Fluss, den ich vergessen habe:
function test(t: Object) { }
test('string'); //error
Grundsätzlich ist 'Objekt' nicht kompatibel mit anderen primitiven Typen, ich denke, dass man Sinn macht.
Die 'Generic Resolution Capture' ist definitiv ein Muss für TS!
@fdecampredon Ja, du hast recht. Mit var x = "5"; x = 5;
x wird der abgeleitete Typ auf number
aktualisiert. Durch Hinzufügen der if (true) { }
um die zweite Zuweisung wird der Typechecker dazu verleitet, anzunehmen, dass eine der beiden Zuweisungen gültig ist, weshalb der abgeleitete Typ stattdessen auf number | string
aktualisiert wird.
Der Fehler, den Sie myFile.js line 1 string this type is incompatible with myFile.js line 5 number
ist korrekt, da number | string
den Operator *
nicht unterstützt (die einzigen Operationen, die für einen Unionstyp zulässig sind, sind der Schnittpunkt aller Operationen für alle Arten von Die Union). Um dies zu überprüfen, können Sie es in x += 5
ändern und sehen, dass es kompiliert wird.
Ich habe das Beispiel in meinem Kommentar aktualisiert, um die if (true)
Die 'Generic Resolution Capture' ist definitiv ein Muss für TS!
+1
@Arnavion , nicht sicher, warum Sie {}
gegenüber B | C
bevorzugen würden. Das Ableiten von B | C
erweitert die Anzahl der Programme, die typechecken, ohne die Korrektheit zu beeinträchtigen. Dies ist eine allgemein wünschenswerte Eigenschaft von Typsystemen.
Das Beispiel
declare function foo<T>(x:T, y: T): T;
var r = foo(1, "a"); // no problem, T is just string|number
Bereits unter dem aktuellen Compiler typechecks, außer dass T als {}
und nicht als string | number
. Dies beeinträchtigt nicht die Korrektheit, ist aber im Großen und Ganzen weniger nützlich.
Die Schlussfolgerung von number | string
anstelle von {}
erscheint mir nicht problematisch. In diesem speziellen Fall wird die Menge der gültigen Programme nicht erweitert. Wenn die Typen jedoch eine gemeinsame Struktur aufweisen, scheint das Typsystem, das dies erkennt und einige zusätzliche Methoden und / oder Eigenschaften gültig macht, nur eine Verbesserung zu sein.
Das Ableiten von
B | C
erweitert die Anzahl der Programme, die typchecken, ohne die Korrektheit zu beeinträchtigen
Ich denke, dass das Zulassen der + -Operation für etwas, das entweder eine Zeichenfolge oder eine Zahl sein kann, die Korrektheit beeinträchtigt, da die Operationen überhaupt nicht ähnlich sind. Es ist nicht wie in einer Situation, in der die Operation zu einer gemeinsamen Basisklasse gehört (meine Option 2) - in diesem Fall können Sie eine gewisse Ähnlichkeit erwarten.
Der Operator + wäre nicht aufrufbar, da er zwei inkompatible Überladungen hätte - eine, bei der beide Argumente Zahlen sind, und eine, bei der beide Zeichenfolgen sind. Da B | C ist schmaler als Zeichenfolge und Zahl, es wäre in keiner Überladung als Argument zulässig.
Außer Funktionen sind bivariant in Bezug auf ihre Argumente, so dass dies ein Problem sein könnte?
Ich dachte, da var foo: string; console.log(foo + 5); console.log(foo + document);
kompiliert, dass der String + -Operator alles auf der rechten Seite zulässt, hätte string | number
+ <number>
als gültige Operation. Aber du hast recht:
error TS2365: Operator '+' cannot be applied to types 'string | number' and 'number'.
Viele der Kommentare haben sich auf die automatische Erweiterung von Typen in Flow konzentriert. In beiden Fällen können Sie das gewünschte Verhalten erzielen, indem Sie eine Anmerkung hinzufügen. In TS würden Sie bei der Deklaration explizit erweitern: var x: number|string = 5;
und in Flow würden Sie bei der Deklaration einschränken: var x: number = 5;
. Ich denke, der Fall, für den keine Typdeklaration erforderlich ist, sollte der am häufigsten verwendete sein. In meinen Projekten würde ich erwarten, dass var x = 5; x = 'five';
häufiger ein Fehler ist als ein Unionstyp. Also würde ich sagen, dass TS die Schlussfolgerung in diesem Fall richtig verstanden hat.
Was die Flow-Funktionen betrifft, die meiner Meinung nach am wertvollsten sind?
string!
als als den nullbaren Modifikator ?string
Flow. Ich sehe drei Probleme damit:undefined
mit mixed
und Object
.Object.keys(3)
in Ihrem Browser und Sie erhalten eine Fehlermeldung. Dies ist jedoch nicht kritisch, da ich denke, dass es nur wenige Randfälle gibt.Zur automatischen Vereinigungstypinferenz: Ich gehe davon aus, dass "Typinferenz" auf die Typdeklaration beschränkt ist. Ein Mechanismus, der implizit auf eine ausgelassene Typdeklaration folgt. Wie :=
in Go. Ich bin kein Typtheoretiker, aber nach meinem Verständnis ist Typinferenz ein Compiler-Pass, der jeder impliziten Variablendeklaration (oder jedem Funktionsargument) eine explizite Typanmerkung hinzufügt, die aus dem Typ des Ausdrucks abgeleitet wird, von dem sie zugewiesen wird. Soweit ich weiß, funktioniert dies auf diese Weise für jeden anderen Inferenzmechanismus. C #, Haskell, Go, alle arbeiten so. Oder nicht?
Ich verstehe das Argument, dass JS im wirklichen Leben die TS-Semantik diktieren darf, aber dies ist vielleicht ein guter Punkt, um stattdessen anderen Sprachen zu folgen. Typen sind schließlich der einzige entscheidende Unterschied zwischen JS und TS.
Ich mag viele der Flux-Ideen, aber diese, na ja, wenn es tatsächlich so gemacht wird ... das ist einfach komisch.
Nicht-Null-Typen scheinen für ein modernes Typsystem ein obligatorisches Merkmal zu sein. Wäre es einfach, ts hinzuzufügen?
Weitere Informationen zur Komplexität des Hinzufügens von nicht nullbaren Typen zu TS finden Sie unter https://github.com/Microsoft/TypeScript/issues/185
Es genügt zu sagen, dass die überwiegende Mehrheit der gängigen Sprachen heutzutage so schön wie nicht nullfähige Typen ist, dass sie standardmäßig keine nicht nullbaren Typen haben (wo das Feature wirklich glänzt) oder überhaupt keine allgemeine nicht nullbare Funktion. Und nur wenige, wenn überhaupt, haben versucht, es nachträglich hinzuzufügen (oder erfolgreich hinzuzufügen), aufgrund der Komplexität und der Tatsache, dass so viel vom Wert der Nicht-Nullbarkeit darin liegt, dass es der Standardwert ist (ähnlich wie bei der Unveränderlichkeit). Dies bedeutet nicht, dass wir die Möglichkeiten hier nicht in Betracht ziehen, aber ich würde es auch nicht als obligatorische Funktion bezeichnen.
So sehr ich den Nicht-Null-Typ vermisse, ist die eigentliche Funktion, die ich beim Flow vermisse, die generische Erfassung. Die Tatsache, dass ts jedes Generikum in {}
auflöst, macht es wirklich schwierig, es mit einem funktionalen Konstrukt zu verwenden, insbesondere Currying.
Persönlich sind generische Erfassung und Nicht-Nullfähigkeit _hochwertige Ziele von Flow. Ich werde den anderen Thread lesen, aber ich wollte auch meine 2c hier reinwerfen.
Ich habe manchmal das Gefühl, dass der Vorteil des Hinzufügens der Nicht-Nullfähigkeit fast jeden Preis wert ist. Es handelt sich um eine Fehlerbedingung mit so hoher Wahrscheinlichkeit, und obwohl die Standardnullbarkeit den integrierten Wert derzeit schwächt, fehlt TypeScript die Möglichkeit, die Nullfähigkeit überhaupt zu diskutieren, indem einfach angenommen wird, dass dies überall der Fall ist.
Ich würde jede Variable, die ich in einem Herzschlag als nicht nullbar finden könnte, mit Anmerkungen versehen.
Es gibt ziemlich viele versteckte Funktionen in Flow, die auf der Flow-Site nicht dokumentiert sind. Einschließlich SuperType-gebundener und existenzieller Typ
Hilfreichster Kommentar
Persönlich sind generische Erfassung und Nicht-Nullfähigkeit _hochwertige Ziele von Flow. Ich werde den anderen Thread lesen, aber ich wollte auch meine 2c hier reinwerfen.
Ich habe manchmal das Gefühl, dass der Vorteil des Hinzufügens der Nicht-Nullfähigkeit fast jeden Preis wert ist. Es handelt sich um eine Fehlerbedingung mit so hoher Wahrscheinlichkeit, und obwohl die Standardnullbarkeit den integrierten Wert derzeit schwächt, fehlt TypeScript die Möglichkeit, die Nullfähigkeit überhaupt zu diskutieren, indem einfach angenommen wird, dass dies überall der Fall ist.
Ich würde jede Variable, die ich in einem Herzschlag als nicht nullbar finden könnte, mit Anmerkungen versehen.