Typescript: Vorschlag: Holen Sie sich den Typ eines beliebigen Ausdrucks mit typeof

Erstellt am 25. Jan. 2016  ·  157Kommentare  ·  Quelle: microsoft/TypeScript

Arbeitsimplementierung für diesen Vorschlag

Probieren Sie es aus: npm install yortus-typescript-typeof

Sehen Sie sich den Unterschied an: hier .

Problemszenario

Die Typinferenz von TypeScript deckt die meisten Fälle sehr gut ab. Es bleiben jedoch einige Situationen, in denen es keine offensichtliche Möglichkeit gibt, auf einen anonymen Typ zu verweisen, obwohl der Compiler ihn ableiten kann. Einige Beispiele:

Ich habe eine stark typisierte Sammlung, aber der Elementtyp ist anonym/unbekannt, wie kann ich auf den Elementtyp verweisen? (#3749)
// Mapping to a complex anonymous type. How to reference the element type?
var data = [1, 2, 3].map(v => ({ raw: v, square: v * v }));
function checkItem(item: typeof data[0] /* ERROR */) {...}

// A statically-typed dictionary. How to reference a property type?
var things = { 'thing-1': 'baz', 'thing-2': 42, ... };
type Thing2Type = typeof things['thing-2']; // ERROR

// A strongly-typed collection with special indexer syntax. How to reference the element type?
var nodes = document.getElementsByTagName('li');
type ItemType = typeof nodes.item(0); // ERROR
Eine Funktion gibt einen lokalen/anonymen/unzugänglichen Typ zurück, wie kann ich auf diesen Rückgabetyp verweisen? (#4233, #6179, #6239)
// A factory function that returns an instance of a local class
function myAPIFactory($http: HttpSvc, id: number) {
    class MyAPI {
        constructor(http) {...}
        foo() {...}
        bar() {...}
        static id = id;
    }
    return new MyAPI($http);
}
function augmentAPI(api: MyAPI /* ERROR */) {...}
Ich habe eine Schnittstelle mit einer komplexen anonymen Form. Wie kann ich auf die Typen ihrer Eigenschaften und Untereigenschaften verweisen? (#4555, #4640)
// Declare an interface DRY-ly and without introducing extra type names
interface MyInterface {
    prop1: {
        big: {
            complex: {
                anonymous: { type: {} }
            }
        }
    },

    // prop2 shares some structure with prop1
    prop2: typeof MyInterface.prop1.big.complex; // ERROR
}

Warum müssen wir auf anonyme/abgeleitete Typen verweisen?

Ein Beispiel ist das Deklarieren einer Funktion, die einen anonymen Typ als Parameter akzeptiert. Wir müssen den Typ irgendwie in der Typanmerkung des Parameters referenzieren, andernfalls muss der Parameter als any eingegeben werden.

Aktuelle Problemumgehungen

Deklarieren Sie eine Dummy-Variable mit einem Initialisierer, der den gewünschten Typ herleitet, ohne den Ausdruck auszuwerten (dies ist wichtig, weil wir keine Nebeneffekte zur Laufzeit haben wollen, sondern nur Typrückschluss). Zum Beispiel:

let dummyReturnVal = null && someFunction(0, ''); // NB: someFunction is never called!
let ReturnType = typeof dummyReturnVal;           // Now we have a reference to the return type

Diese Problemumgehung hat einige Nachteile:

  • ganz klar ein Kludge, unklar für die Leser
  • Bezeichnerverschmutzung (muss eine Variable wie dummyReturnValue einführen)
  • funktioniert nicht in Umgebungskontexten, da es eine zwingende Anweisung erfordert

Vorgeschlagene Lösung

_(NB: Diese Lösung wurde bereits in Nr. 4233 vorgeschlagen, aber dieses Problem ist mit „Vorschlag erforderlich“ gekennzeichnet, und es gibt mehrere andere eng verwandte Probleme, daher dieses separate Problem.)_

Der Operand von typeof darf ein beliebiger Ausdruck sein. Dies ist bereits für typeof expr in einer Value-Position wie if (typeof foo() === 'string') erlaubt. Dieser Vorschlag erlaubt aber auch einen beliebigen Ausdruck, wenn typeof an einer Typposition als Typabfrage verwendet wird, zB type ElemType = typeof list[0] .

Dieser Vorschlag orientiert sich bereits eng am aktuellen Wortlaut der Spezifikation:

Typabfragen sind nützlich, um anonyme Typen zu erfassen, die von verschiedenen Konstrukten wie Objektliteralen, Funktionsdeklarationen und Namespacedeklarationen generiert werden.

Dieser Vorschlag erweitert diese Nützlichkeit also nur auf die derzeit unversorgten Situationen wie in den obigen Beispielen.

Syntax und Semantik

Die Semantik ist genau wie bereits in der Spezifikation 4.18.6 angegeben :

Der 'typeof'-Operator nimmt einen Operanden eines beliebigen Typs und erzeugt einen Wert vom primitiven Typ String. An Stellen, an denen ein Typ erwartet wird, kann 'typeof' auch in einer Typabfrage (Abschnitt 3.8.10) verwendet werden, um den Typ eines Ausdrucks zu erzeugen.

Der vorgeschlagene Unterschied bezieht sich auf den unten zitierten Abschnitt 3.8.10 , wo der durchgestrichene Text entfernt und der fettgedruckte Text hinzugefügt würde:

Eine Typabfrage besteht aus dem Schlüsselwort typeof gefolgt von einem Ausdruck. Der Ausdruck wird als Bezeichnerausdruck (Abschnitt 4.3) oder Eigenschaftszugriffsausdruck (Abschnitt 4.13) verarbeitet , ein unärer Ausdruck , dessen erweiterter Typ (Abschnitt 3.12) das Ergebnis wird. Ähnlich wie bei anderen statischen Typisierungskonstrukten werden Typabfragen aus dem generierten JavaScript-Code gelöscht und fügen keinen Laufzeit-Overhead hinzu.

Ein Punkt, der betont werden muss (von dem ich dachte, dass er auch in der Spezifikation stand, ihn aber nicht finden kann), ist, dass Typabfragen ihren Operanden nicht auswerten. Das gilt derzeit und würde auch für komplexere Ausdrücke gelten.

Dieser Vorschlag führt keine neue Syntax ein, er macht typeof nur weniger restriktiv in den Arten von Ausdrücken, die abgefragt werden können.

Beispiele

// Mapping to a complex anonymous type. How to reference the element type?
var data = [1, 2, 3].map(v => ({ raw: v, square: v * v }));
function checkItem(item: typeof data[0]) {...} // OK: item type is {raw:number, square:number}

// A statically-typed dictionary. How to reference a property type?
var things = { 'thing-1': 'baz', 'thing-2': 42, ... };
type Thing2Type = typeof things['thing-2']; // OK: Thing2Type is number

// A strongly-typed collection with special indexer syntax. How to reference the element type?
var nodes = document.getElementsByTagName('li');
type ItemType = typeof nodes.item(0); // OK: ItemType is HTMLLIElement

// A factory function that returns an instance of a local class
function myAPIFactory($http: HttpSvc, id: number) {
    class MyAPI {
        constructor(http) {...}
        foo() {...}
        bar() {...}
        static id = id;
    }
    return new MyAPI($http);
}
type MyAPI = typeof myAPIFactory(null, 0); // OK: MyAPI is myAPIFactory's return type
function augmentAPI(api: MyAPI) {...} // OK

// Declare an interface DRY-ly and without introducing extra type names
interface MyInterface {
    prop1: {
        big: {
            complex: {
                anonymous: { type: {} }
            }
        }
    },

    // prop2 shares some structure with prop1
    prop2: typeof (<MyInterface>null).prop1.big.complex; // OK: prop2 type is {anonymous: {type: {}}}
}

Diskussion der Vor- und Nachteile

Dagegen : Schlechte Syntaxästhetik. Alternative Syntaxen für Einzelfälle wurden in #6179, #6239, #4555 und #4640 vorgeschlagen.

Für : Andere Syntaxen mögen für ihre spezifischen Fälle besser aussehen, aber sie unterscheiden sich alle voneinander und lösen jeweils nur ein bestimmtes Problem. Dieser Vorschlag löst die in all diesen Fragen aufgeworfenen Probleme, und der Entwickler muss keine neue(n) Syntax(en) lernen.

Gegen : Ein Ausdruck in einer Typposition ist verwirrend.

Für : TypeScript überlädt typeof bereits mit zwei Bedeutungen, als Typabfrage akzeptiert es bereits einen Ausdruck an einer Typposition und erhält seinen Typ, ohne ihn auszuwerten. Dies lockert nur die Einschränkungen, was dieser Ausdruck sein kann, so dass er die in dieser Ausgabe aufgeworfenen Probleme lösen kann.

Gegen : Dies könnte missbraucht werden, um riesige, lange, mehrzeilige Abfragen zu schreiben.

Für : Es gibt keinen guten Grund, dies in einer Typabfrage zu tun, aber es gibt gute Gründe, komplexere Ausdrücke zuzulassen. Dies ist im Grunde Martin Fowlers Befähigung vs. Regie .

Auswirkungen auf das Design, Fragen und weitere Arbeiten

Kompatibilität

Dies ist eine rein abwärtskompatible Änderung. Der gesamte vorhandene Code ist nicht betroffen. Die Verwendung der zusätzlichen Funktionen von typeof ist Opt-in.

Leistung

Wenn Sie sich das Diff ansehen, können Sie sehen, dass die Änderungen sehr gering sind. Der Compiler kennt bereits die abgefragten Typen, dies zeigt sie nur dem Entwickler an. Ich würde vernachlässigbare Auswirkungen auf die Leistung erwarten, aber ich weiß nicht, wie ich das testen soll.

Werkzeuge

Ich habe VS Code so eingerichtet, dass eine Version von TypeScript verwendet wird, wobei dieser Vorschlag als Sprachdienst implementiert ist, und die gesamte Syntaxhervorhebung und Intellisense ist, soweit ich sie getestet habe, fehlerfrei.

Komplexe Ausdrücke können in .d.ts -Dateien vorkommen

Der Operand typeof könnte ein beliebiger Ausdruck sein, einschließlich eines IIFE, oder ein Klassenausdruck mit Methodenkörpern usw. Ich kann mir keinen Grund dafür vorstellen, es ist einfach kein Fehler mehr, nicht einmal im Inneren eine .d.ts -Datei ( typeof kann in Umgebungskontexten verwendet werden und ist nützlich). Eine Folge dieses Vorschlags ist also, dass "Aussagen können nicht in Umgebungskontexten erscheinen" nicht mehr streng richtig ist.

Rekursive Typen werden robust behandelt

Der Compiler scheint bereits über die gesamte Logik zu verfügen, die erforderlich ist, um mit solchen Dingen umzugehen:

function foo<X,Y>(x: X, y: Y) {
    var result: typeof foo(x, y); // ERROR: 'result' is referenced in its own type annotation
    return result;
}
Kann den Rückgabetyp einer überladenen Funktion abfragen

Es ist nicht zweideutig; Es wählt die Überladung aus, die dem Ausdruck der Abfrage entspricht:

declare function foo(a: boolean): string;
declare function foo(a: number): any[];
type P = typeof foo(0);    // P is any[]
type Q = typeof foo(true); // Q is string
Suggestion Too Complex

Hilfreichster Kommentar

Öffnete eine PR unter #17961.

Alle 157 Kommentare

Für alle, die schnell damit in VS Code mit Intellisense usw. spielen möchten, finden Sie hier ein Spielplatz-Repo .

Typ P = Typ von foo (0); // P ist beliebig[]
Typ Q = Typ von foo(true); // Q ist eine Zeichenfolge

Ich denke, die Verwendung von Typen als Argument anstelle von Werten ist eine gültigere Syntax.

type P = typeof foo(number);    // P is any[]
type Q = typeof foo(boolean); // Q is string

Es ist klarer, dass die Funktion nicht aufgerufen wird, weil Sie Typen und keine Werte als Argumente angeben. Der andere Punkt ist, dass es weniger zweideutig ist. Einige Leute verwenden typeof foo(false) , während andere typeof foo(true) verwenden. Wenn Sie Typen als Argumente haben, können die Leute nur typeof foo(boolean) schreiben.

@tinganho genau!
Obwohl wir immer noch typeof foo("abc") mit #5185 schreiben können
hier ist "abc" der Singleton-String-Typ

@tinganho Ich habe über Ihre Idee nachgedacht und sehe einige Dinge, die ich an diesem Vorschlag bevorzuge, und andere Dinge, die ich an Ihrem Vorschlag bevorzuge. Ihr Vorschlag ist aus den von Ihnen angegebenen Gründen gut (einfachere, klarere Syntax, weniger mehrdeutig, sieht weniger wie ein Funktionsaufruf aus). Was ich an meinem Vorschlag bevorzuge, ist, dass er keine neuartige Syntax einführt, also keine Komplikationen zum Parser/Prüfer hinzufügt und auch komplexere Szenarien unterstützt, in denen Sie keine einfachen Typnamen für die Argumente haben.

Ich dachte, was wäre, wenn es eine sehr verkürzte Art gäbe, so etwas wie Ihre foo(number) -Syntax zu schreiben, aber die vorhandene Ausdrucks-Parsing-Mechanik zu verwenden? Also habe ich als Experiment einen neuen Ausdruck eingeführt: _unary as_. Sie können einfach as T schreiben und das ist eine Abkürzung für (null as T) . Sie sagen im Grunde: _'Ich interessiere mich nicht für den Wert, aber ich möchte, dass der Ausdruck den Typ X hat'_.

Mit dieser Änderung (die ich im Playground-Repo implementiert habe) können Sie etwas schreiben, das Ihrer vorgeschlagenen Syntax viel näher kommt, aber es wird immer noch als gewöhnlicher Ausdruck geparst:

type P = typeof foo(as number);    // P is any[]
type Q = typeof foo(as boolean); // Q is string

let prop2: typeof (as MyInterface).prop1.big.complex; // prop2 type is {anonymous: {type: {}}}

Das war nur ein schnelles Experiment. Eine äquivalente Syntax könnte sein (aber ich habe das nicht implementiert):

type P = typeof foo(<number>);    // P is any[]
type Q = typeof foo(<boolean>); // Q is string

let prop2: typeof (<MyInterface>).prop1.big.complex; // prop2 type is {anonymous: {type: {}}}

Die zweite Syntax könnte als _Nullary Type Assertion_ bezeichnet werden, wobei der Ausdruck <T> eine Abkürzung für (<T> null) ist.

@yortus , wir haben letzte Woche einige Zeit damit verbracht, über diesen Vorschlag zu sprechen. sorry, dass ich nicht früher gepostet habe. der Konsens war 1. Wir haben das Problem, dass wir uns auf einige Typen nicht beziehen können, zB Rückgabe einer Funktion oder Instanztyp eines Klassenausdrucks. und 2. das Hinzufügen von Ausdrücken in einer Textposition ist etwas, womit wir uns nicht wohl fühlen.

Der Vorschlag von @tinganho war einer, über den wir auch gesprochen haben. Ich denke, es ist schmackhafter, obwohl es wahrscheinlich komplizierter zu implementieren wäre. Das Hinzufügen eines neuen unären Operators oder die Verwendung von Cast-Syntax ist nicht wirklich elegant, da nur die Typnamen verwendet werden.

Heute auf der Slog lange diskutiert. "PRs akzeptieren" ist hier "PRs akzeptieren, wenn die Umsetzung nicht zu verrückt wird"

Der Vorschlag von @tinganho sieht ziemlich gut aus (zumindest im Vergleich zu den anderen Optionen) und wir würden gerne eine vorläufige PR sehen, die dies umsetzt.

Das Schwierige ist, dass wir keinen vollständig separaten Codepfad zum Auflösen des Rückgabetyps von f(number) vs. f(0) haben wollen, aber der Überladungsauflösungsalgorithmus ist vollständig mit der Annahme integriert, dass dies der Fall ist es arbeitet mit einer Reihe von _Ausdrücken_ statt mit einer Reihe von _Typen_. Aber wir denken, mit ein wenig Trickserei sollte dies einfach sein.

Der grundlegende Angriffsplan wäre:

  • Erweitern Sie die Grammatik beim Analysieren typeof , um Dinge zuzulassen, die wie Funktionsaufrufe, Zugriff auf Eigenschaften und Zugriff auf indizierte Eigenschaften aussehen
  • Wenn Sie die Argumente für einen Funktionsaufruf in einer Typabfrage analysieren (nennen wir sie _psuedocalls_ / _psuedoarguments_), verwenden Sie die Funktion parseType . Dadurch wird ein TypeNode erstellt, aber ein Flag auf dem Knoten gesetzt, das angibt, dass es sich um einen Typ handelt, der im Kontext einer Typabfrage analysiert wurde
  • Im Checker prüft checkExpression auf dieses Flag und ruft anstelle der normalen Ausdrucksverarbeitung getTypeFromTypeNode auf

@mhegazy , @RyanCavanaugh bin mir nicht sicher, wie viele Eckfälle das Team besprochen hat, also kann ich hier ein paar zur Klarstellung ansprechen? Ich habe unten eine Reihe von Beispielen aufgelistet und jedes mit dem kommentiert, was meiner Meinung nach das Ergebnis der typeof -Operation sein sollte, mit Fragezeichen in fragwürdigen Fällen.


Indexer-Notation
var data = [1, 2, 3];
const LOBOUND = 0;
type Elem1 = typeof data[0];        // number
type Elem2 = typeof data[999999];   // number or ERROR?
type Elem3 = typeof data[1+2];      // ERROR or number?
type Elem4 = typeof data[LOBOUND];  // ERROR or number?

var tuple: [number, string] = [123, 'abc'];
type Elem4 = typeof tuple[0];       // number or number|string?
type Elem5 = typeof tuple[1];       // string or number|string?
type Elem6 = typeof tuple[999999];  // number|string or ERROR?

const ABC: 'a-b-c' = 'a-b-c';
let dict = { 'a-b-c': 123, 'd-e-f': true };
type Prop1 = typeof dict['a-b-c'];  // number
type Prop2 = typeof dict['d-e-f'];  // boolean
type Prop3 = typeof dict[ABC];      // ERROR or number or any?

Notation des Funktionsrückgabetyps
// A simple function
declare function f1(n: number): string[];
type Ret1 = typeof f1(number);      // string[]
type Ret2 = typeof f1(0);           // ERROR or string[]?

// An asynchronous function that either accepts a callback or returns a Promise
declare function f2(n: number): Promise<string[]>;
declare function f2(n: number, cb: (err?: any, result?: string[]) => void): void;
type Ret3 = typeof f2(number);                    // Promise<string[]>
type Ret4 = typeof f2(number, any);               // void
type Ret5 = typeof f2(number, Function);          // ERROR: Function not assignable to callback
type Ret6 = typeof f2(number, (err: any, result: string[]) => void); // void
type Ret7 = typeof f2(number, (...args) => any);  // void

// A special function-like object
interface Receiver {
    (data: string[]): void;
    transmogrify(): number[];
}
declare function f3(n: number, receiver: Receiver): Promise<void>;
declare function f3(n: number, callback: (err?: any, result?: string[]) => void): void;
type Ret8 = typeof f3(number, Receiver);           // Promise<void>
type Ret9 = typeof f3(number, any);                // ambiguous? or picks first overload?
type Ret10 = typeof f3(number, Function);          // ERROR
type Ret11 = typeof f3(number, (...args) => any);  // void since not assignable to Receiver

// A function with parameter destructuring
interface CartesianCoordinate {/***/}
interface PolarCoordinate {/***/}
declare function f4({ x: number, y: number }): CartesianCoordinate;
declare function f4({ r: number, t: number }): PolarCoordinate;
type Ret12 = typeof f4(any);        // ambiguous? or picks first overload?
type Ret13 = typeof f4({x;y});      // CartesianCoordinate
type Ret14 = typeof f4({r;t});      // PolarCoordinate
type Ret15 = typeof f4({x;r;t;y});  // ambiguous? or picks first overload?

// Type-ception: is there anything wrong with typeof-in-typeof?
declare function f5(n: number, receiver: Receiver): Promise<void>;
declare function f5(n: number, callback: (err?: any, result?: string[]) => void): void;
function myCallback(err, result) {/***/}
var myReceiver: Receiver;
type Ret16 = typeof f5(number, typeof myReceiver); // Promise<void>
type Ret17 = typeof f5(number, typeof myCallback); // void


Extrahieren eines Teils eines Klassen-/Schnittstellentyps

Das heißt, die obigen typeof (<MyInterface> null).prop1.big.complex; Beispiele.

Ich entnehme den obigen Kommentaren, dass dies außerhalb des Geltungsbereichs liegt und nicht unterstützt wird. Ist das korrekt?

Indexer-Notation
var data = [1, 2, 3];
const LOBOUND = 0;
type Elem1 = typeof data[0];        // number
type Elem2 = typeof data[999999];   // number
type Elem3 = typeof data[1+2];      // ERROR, only literals allowed here
type Elem4 = typeof data[LOBOUND];  // number when const resolution is done, otherwise any

var tuple: [number, string] = [123, 'abc'];
type Elem4 = typeof tuple[0];       // number
type Elem5 = typeof tuple[1];       // string 
type Elem6 = typeof tuple[999999];  // number|string

const ABC: 'a-b-c' = 'a-b-c';
let dict = { 'a-b-c': 123, 'd-e-f': true };
type Prop1 = typeof dict['a-b-c'];  // number
type Prop2 = typeof dict['d-e-f'];  // boolean
type Prop3 = typeof dict[ABC];      // number when const resolution work is done, otherwise any

Notation des Funktionsrückgabetyps
// A simple function
declare function f1(n: number): string[];
type Ret1 = typeof f1(number);      // string[]
type Ret2 = typeof f1(0);           // error, 0 is not a type

// An asynchronous function that either accepts a callback or returns a Promise
declare function f2(n: number): Promise<string[]>;
declare function f2(n: number, cb: (err?: any, result?: string[]) => void): void;
type Ret3 = typeof f2(number);                    // Promise<string[]>
type Ret4 = typeof f2(number, any);               // void
type Ret5 = typeof f2(number, Function);          // ERROR: Function not assignable to callback
type Ret6 = typeof f2(number, (err: any, result: string[]) => void); // void
type Ret7 = typeof f2(number, (...args) => any);  // void

// A special function-like object
interface Receiver {
    (data: string[]): void;
    transmogrify(): number[];
}
declare function f3(n: number, receiver: Receiver): Promise<void>;
declare function f3(n: number, callback: (err?: any, result?: string[]) => void): void;
type Ret8 = typeof f3(number, Receiver);           // Promise<void>
type Ret9 = typeof f3(number, any);                // picks first overload
type Ret10 = typeof f3(number, Function);          // ERROR
type Ret11 = typeof f3(number, (...args) => any);  // void since not assignable to Receiver

// A function with parameter destructuring
interface CartesianCoordinate {/***/}
interface PolarCoordinate {/***/}
declare function f4({ x: number, y: number }): CartesianCoordinate;
declare function f4({ r: number, t: number }): PolarCoordinate;
type Ret12 = typeof f4(any);        // picks first overload
type Ret13 = typeof f4({x;y});      // CartesianCoordinate
type Ret14 = typeof f4({r;t});      // PolarCoordinate
type Ret15 = typeof f4({x;r;t;y});  // picks first overload

// Type-ception: is there anything wrong with typeof-in-typeof?
declare function f5(n: number, receiver: Receiver): Promise<void>;
declare function f5(n: number, callback: (err?: any, result?: string[]) => void): void;
function myCallback(err, result) {/***/}
var myReceiver: Receiver;
type Ret16 = typeof f5(number, typeof myReceiver); // Promise<void>
type Ret17 = typeof f5(number, typeof myCallback); // void

Bin gespannt was hier passiert:

const number = "number";
type Ret3 = typeof f2(number); // What happens here?

@SaschaNaz gute Frage. Eine ähnliche Situation:

class MyClass { foo; bar; }
declare function f(inst: MyClass): number;
type Ret = typeof f(MyClass);     // number (presumably)

In diesem Fall ist es in typeof f(MyClass) $ sinnvoll, dass der MyClass _type_ vor dem MyClass _value_ (also der Konstruktorfunktion) betrachtet wird. Ersteres führt zu Ret = number , letzteres würde zu etwas wie error: MyClass is not a type führen.

Würde die gleiche Logik für einen Namen gelten, der sich sowohl auf einen _Typ_ als auch auf einen _Konstantenwert_ bezieht? In Ihrem Beispiel würde das bedeuten, dass der Typ number immer Vorrang vor dem konstanten Wert number hat. Irgendwelche Gedanken @RyanCavanaugh?

Richtig, wir würden dies mit der üblichen Semantik eines Typausdrucks auflösen (als ob Sie var x: [whatever] geschrieben hätten). Sie könnten also typeof f(MyClass) auf den Aufruf f mit der Instanzseite und typeof f(typeof MyClass) auf den Aufruf f mit der Konstruktorfunktion beziehen.

Das Beispiel von @SaschaNaz bezieht sich also eindeutig auf number als _type_, nicht als _const value_, richtig?

const number = "number";
type Ret3 = typeof f2(number); // Promise<string[]>

@RyanCavanaugh können Sie bestätigen, dass die dritte Gruppe von Anwendungsfällen außerhalb des Geltungsbereichs liegt? zB aus dem OP:

// Declare an interface DRY-ly and without introducing extra type names
interface MyInterface {
    prop1: {
        big: {
            complex: {
                anonymous: { type: {} }
            }
        }
    },

    // prop2 shares some structure with prop1
    prop2: typeof (<MyInterface>null).prop1.big.complex; // OK: prop2 type is {anonymous: {type: {}}}
}

Dieser Anwendungsfall (mit welcher Syntax auch immer) wird derzeit nicht unterstützt, ist das richtig?

Ich denke, dies sollte durch das Zulassen von this -Ausdrücken abgedeckt werden.

   prop2: typeof this.prop1.big.complex;

Ich denke, das const Ding sollte durch ein weiteres typeof gelöst werden.

type Ret3 = typeof f2(typeof number); // typeof number is string so error here

... während dies typeof data[LOBOUND] blockieren würde.

@mhegazy das ist eine tolle Idee zu typeof this . Ich habe gerade festgestellt, dass dies bereits in der gegabelten Implementierung funktioniert, die ich für diesen Vorschlag gemacht habe. Nun, es funktioniert für den Unterricht. Für Schnittstellen gibt es keinen Fehler, aber der Typ this wird immer als any abgeleitet. Stromausgang vom gegabelten Impl:

class MyClass {
    prop1: {
        big: {
            complex: {
                anonymous: { type: {} }
            }
        }
    };

    prop2: typeof this.prop1.big.complex; // prop2 type is {anonymous: {type: {}}}
}

interface MyInterface {
    prop1: {
        big: {
            complex: {
                anonymous: { type: {} }
            }
        }
    };

    prop2: typeof this.prop1.big.complex; // prop2 type is any
}

Ist das Ableiten this innerhalb von Schnittstellendeklarationen eine Möglichkeit oder bleibt diese Funktion auf Klassen beschränkt?

Ich möchte zwei Punkte zu #6179 und Angular machen.

// A factory function that returns an instance of a local class
function myAPIFactory($http: HttpSvc, id: number) {
    class MyAPI {
        constructor(token: string) {...}
        foo() {...}
        bar() {...}
        static id = id;
    }
    return MyAPI;
}
type MyAPIConstructor = typeof myAPIFactory(null, 0); // OK: MyAPI is myAPIFactory's return type
function augmentAPI(api: MyAPIConstructor) {...} // OK
  1. Die Anzahl der Parameter kann groß sein. Sagen wir 15 Parameter. Gleichzeitig gibt es keine Überladungen, und es sind nur die Überladungen, für die die Parameter in typeof benötigt werden. Können wir uns für diesen Fall wahrscheinlich eine Syntax wie die folgende vorstellen?

    type MyAPI = typeof myAPIFactory(...);
    
  2. Die Factory-Funktion wird normalerweise keiner eigenen globalen Variablen zugewiesen. Es wird ein Funktionsausdruck verwendet:

    angular.module('app').factory('MyAPI', function($http: HttpSvc, id: number) { /*...*/ });
    

    So sieht es normalerweise aus. Wie man sieht, kann typeof hier überhaupt nicht verwendet werden.

@thron0 Meine Intuition ist, dass etwas, das mehr als ein paar Parameter hat, aber _auch_ einen anonymen Typ zurückgibt, sehr selten sein wird. Was denken Sie?

Es wird selten, wenn Angular 1 selten wird, nicht früher.

Im Grunde ist das, was Sie beschrieben haben, eine typische Angular-Fabrik:

ein Ding, das mehr als ein paar Parameter hat, aber auch einen anonymen Typ zurückgibt

Es ist eine Funktion, die Abhängigkeiten als Parameter nimmt und eine Instanz eines _Dienstes_ erstellt. Man könnte denken, dass eine große Anzahl von Parametern mühsam sein kann, aber die Sache ist, dass diese Fabriken nie direkt aufgerufen werden. Der DI-Container ruft sie auf. Die Factory wird aufgerufen, wenn etwas seinen Rückgabewert (den Dienst) als Abhängigkeit benötigt. Und es wird nur einmal aufgerufen, da Dienste in Angular immer Singletons sind. Ein Dienst kann jedoch alles sein. Wenn wir also ein Nicht-Singleton-Verhalten benötigen, kann die Factory einen Konstruktor (oder eine Factory-Funktion) zurückgeben. Genau wie in diesem Codebeispiel:

angular.module('app').factory('MyAPI', function($http: HttpSvc, id: number) {
    class MyAPI {
        constructor(token: string) {...}
        foo() {...}
        bar() {...}
        static id = id;
    }
    return MyAPI;
});

So sieht mein aktueller Workaround für diese Situation aus:

class MyAPI {
    // dependencies (1)
    protected $http: HttpSvc;
    protected id: number;

    constructor(token: string) {...}
    foo() {...}
    bar() {...}
    // Static properties cannot be used with this approach because:
    // 1) this class is a global variable so it can be shared by different 
    // instances of the DI container,
    // 2) the id property isn't available here as it's initialized in the subclass
    //// static id0 = id;
}

angular.module('app').factory('MyAPI', function($http: HttpSvc, id: number) { // (2)
    return class extends MyAPI {
        $http = $http; // (3)
        id = id;
    };
});

// this type is needed for type annotations in the services that require MyAPI as a dependency
type MyAPIConstructor = typeof MyAPI;

angular.module('app').factory('someOtherService', function(MyAPI: MyAPIConstructor) {
    var api = new MyAPI('qwerty');
    /* ... */
});

Wie Sie sehen können, ist es total hässlich und schmerzhaft. Ich muss die Abhängigkeiten dreimal auflisten. Wenn ich mehr Abhängigkeiten habe, ist es manchmal einfacher, die Klasse auf die übliche Weise (innerhalb der Factory-Funktion) zu schreiben und eine Schnittstelle für ihre Verbraucher zu deklarieren.

Hallo, ich möchte vorschlagen, die Syntax/Semantik des Typs intakt zu lassen und stattdessen eine einfache Typzugriffsalgebra zu implementieren:

Stellen wir uns die Erweiterung der Typsyntax vor:

type          ::=  ... |  literalType | type typeAccess | guarded
guarded    ::= "type" id
literalType   ::= stringLiteral | numberLiteral | symLiteral | booleanLiteral 
typeAccess    ::= typeField | typeSubscript | typeCall
typeField     ::= "." id
typeSubscript ::= "[" type "]"
typeCall      ::= "(" [type {"," type}] ")"

Jede Kennung im Geltungsbereich kann gleichzeitig an die beiden Entitäten gebunden werden:

  • Kompilierzeittyp
  • Laufzeitwert

der Typwächter extrahiert nur ersteres. Daher

class A {}
var a: A;

ist äquivalent zu

class A {}
var a: type A;

in diesem Fall, wenn wir eine Klasse hätten

class ABC {
    a: number;
    b(x: number): string;
    c:string[];
    d:[number, string];
}

dann könnten wir schreiben

var a: (type ABC).a; // number
var b: (type ABC).b(number); // string
var c: (type ABC).c[number]; // string
var d: (type ABC).c[0]; // number

Es behandelt auch das Zusammenführen von _Typ_-Entitäten und _Wert_-Entitäten unter demselben Bezeichner im lexikalischen Bereich.

interface SAME {
    x: number;
}
namespace SAME: {
    export type x = boolean;
    export var x: string;
}

var a: SAME.x   // boolean
var b: (type SAME).x  // number
var b: (typeof SAME).x  // string

@RyanCavanaugh , @sandersn kannst du diese Idee untersuchen?

Wie erfassen Sie mit Ihrem Vorschlag die Art des Ausdrucks?

Am Donnerstag, den 19. Mai 2016 um 12:26 Uhr schrieb Anatoly Ressin, [email protected] :

Hallo, ich möchte vorschlagen, die Art der Syntax/Semantik intakt zu lassen und
Implementieren Sie stattdessen eine einfache Typzugriffsalgebra:

Stellen wir uns die Erweiterung der Typsyntax vor:

geben Sie ::= ... | ein literalTyp | geben Sie typeAccess | ein bewacht
bewacht ::= "Typ"-ID
literalType ::= stringLiteral | ZahlLiteral | symLiteral | booleanLiteral
typeAccess ::= typeField | typeSubscript | TypCall
typeField ::= "." Ich würde
typeSubscript ::= "[" type "]"
typeCall ::= "(" [type {"," type}] ")"

Jede Kennung im Bereich kann gleichzeitig an die beiden gebunden werden
Entitäten:

  • Kompilierzeittyp
  • Laufzeitwert

der _type_ guard extrahiert nur ersteres. Daher

Klasse a {}
a: A

ist äquivalent zu

Klasse a {}
a: Typ A

in diesem Fall, wenn wir eine Klasse hätten

Klasse ABC {
eine Zahl;
b(x: Zahl): Zeichenkette;
c:Zeichenfolge[];
d:[Zahl, Zeichenkette];
}

dann könnten wir schreiben

var a: (Typ ABC).a; // numbervar b: (Typ ABC).b(number); // stringvar c: (Typ ABC).c[Zahl]; // stringvar d: (Typ ABC).c[0]; // Anzahl

Es behandelt auch das Zusammenführen von _Typ_-Entitäten und _Wert_-Entitäten unter der
gleiche Kennung im lexikalischen Geltungsbereich.

Schnittstelle GLEICH {
x: Zahl;
} Namensraum GLEICH: {
Exporttyp x = boolean;
export var x: string;
}
var a: SAME.x // booleanvar b: (type of SAME).x // numbervar b: (typeof SAME).x // string

@RyanCavanaugh https://github.com/RyanCavanaugh , @sandersn
https://github.com/sandersn können Sie diese Idee untersuchen?


Sie erhalten dies, weil Sie diesen Thread abonniert haben.
Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an
https://github.com/Microsoft/TypeScript/issues/6606#issuecomment -220378019

Ihr Beispiel erinnert mich daran, dass ein Typ eines gepunkteten Ausdrucks und des Typs Eigenschaftstyp # 1295 ein ziemlich ähnlicher Vorschlag ist. Nur dass der eine eher statisch, der andere dynamischer ist.

Ich mag auch die Idee, das Schlüsselwort typeof zu überspringen.

   prop2: this.prop1.big.complex;

@mhegazy in deinem Beispiel verwendest du typeof in einem this-expression :

prop2: typeof this.prop1.big.complex;

Da wir dies heute tun können:

someProp: this;

Aus meiner Sicht ist eine Extrapolation der obigen Syntax für mehr gepunktete Eigenschaften:

someProp: this.prop1.prop2.prop3;

Und nicht:

someProp: typeof this.prop1.prop2.prop3;

Und um einfach mit der Extrapolation fortzufahren – warum nicht typeof zusammen überspringen?

someProps: foo(number)

Warum nicht alle zusammen überspringen?

Ein Grund, der mir einfällt, ist, dass es mehrdeutig werden würde, ob Sie auf die Instanzseite einer Klasse oder auf die statische Seite verweisen:

class C {}

// Does 'x' have the instance type of 'C',
// or does it have the shape of its constructor?
let x: C;

Ich möchte den Typ einer Eigenschaft in einer Schnittstelle auch innerhalb einer anderen Schnittstelle referenzieren. Ich habe mein Beispiel als #10588 gepostet, das jetzt zugunsten dieses Tickets geschlossen ist.

Es wäre schön, so etwas zu schreiben:

interface Foo {
  bar: string;
}

interface Box<T> {
  container: T;
}

interface FooWithBoxedProps {
  bar: Box<Foo.bar>; // OR: bar: Box<typeof Foo.bar>;
}

was nur so übersetzt werden würde:

interface Foo {
  bar: string;
}

interface Box<T> {
  container: T;
}

interface FooWithBoxedProps {
  bar: Box<string>;
}

Nun, einige der zuvor erwähnten Szenarien können jetzt mit _indizierten Zugriffstypen_ (#11929) ausgedrückt werden. Kombinieren Sie diese Funktion einfach mit dem standardmäßigen typeof -Ausdruck und Sie erhalten so etwas:

interface MyInterface {
    prop1: {
        big: {
            complex: {
                anonymous: { type: {} }
            }
        }
    },

    // prop2 shares some structure with prop1
    prop2: MyInterface["prop1"]["big"]["complex"];
}

und es funktioniert! (derzeit in Maschinenschrift@next)

Ich denke, die nächste natürliche Sache wäre, etwas Ähnliches zu haben, aber für Funktionen einige "Funktionsaufruftypen" wie diese:

interface FunctionLike{
    (arg1 : number, arg2 : string) : {a : number; b : string;}
}

type ReturnType = FunctionLike(number, string); // {a : number; b : string;} 

also könnten wir es natürlich mit dem Operator typeof kombinieren

interface BigThing {
    testString: string;
    testNumber: number;
    testBoolean: boolean;
}

function getSmallThing(thing:BigThing){
    return {
        testString : thing.testString,
        testBoolean : thing.testBoolean
    }
}

type SmallThing = typeof getSmallThing(BigThing) // {testString: string; testBoolean : boolean}

Und es scheint, dass dies die Syntax ist, die bereits zu Beginn dieses Threads vorgeschlagen wurde (@tinganho). :)
Aber es wäre ein allgemeineres Feature, das sich einfach gut mit dem aktuellen typeof -Operator kombinieren lässt, genau wie es _indizierte Zugriffstypen_ jetzt tun.

Einige Fragen/Zweifel bleiben jedoch bestehen:

  • Bewältigt diese Funktion einige andere Szenarien wie _indizierte Zugriffstypen_? Wenn nicht, lohnt es sich, es zu implementieren?
  • Ich gehe davon aus, dass es in den meisten Fällen nett wäre, überhaupt keine Argumenttypen angeben zu müssen, da das häufigste Szenario lautet: "Ich möchte nur den Typ dieser einen Funktion erhalten, der keine anderen Überladungen hat". Also vielleicht einige einführen neuer spezifischer Operator dafür wäre in Ordnung (zum Beispiel typeofreturn oder returnof )

Scheint technisch machbar zu sein. Es kann erforderlich sein, die Überladungsauflösung / den Typargumentrückschluss umzugestalten, damit Sie die Argumente nicht auflösen müssen. Ich glaube nicht, dass Generika ein allzu großes zusätzliches Problem darstellen. Eine zu stellende Frage ist, was passiert, wenn keine Überladung mit dem Aufruf übereinstimmt.

Ich denke, es ist nicht offensichtlich, ob diese Funktion wünschenswert ist. Wenn eine Funktion einen sehr aufwändigen anonymen Typ zurückgibt, wäre es als Autor nicht hilfreicher, den Typ zu benennen, damit er nicht auf diese Weise referenziert werden muss? Ich gebe zu, dass dies nur auf einer stilistischen Präferenz basiert. Ich nehme an, ich bin mir nicht sicher, wie bequem dies in der Praxis sein wird. Ich denke jedoch, dass es für den Elementzugriff in Ordnung ist.

Typ P = Typ von foo (0); // P ist beliebig[]
Typ Q = Typ von foo(true); // Q ist eine Zeichenfolge

In Bezug auf diese Diskussion darüber, dass auf Parameter von ihren Typen verwiesen werden kann, würde ich vorschlagen, dasselbe auch für die eigentlichen Funktionen zuzulassen.
Kontext: Ich möchte eine Funktion für map über Objekte eingeben:
function map<T, F extends Function>(fn: F, obj: T): { [P in keyof T]: typeof F(T[P]) }
Der Grund, warum ich auf die Funktion mit ihrem Typ F (abgesehen von nur ihrem Namen fn ) verweisen möchte, wäre für Fälle, in denen ihr Name möglicherweise nicht verfügbar ist, z. würde gerne type MapFn<T, F extends Function> = { [P in keyof T]: typeof F(T[P]) } sagen können.

Etwas, das wahrscheinlich viel einfacher zu implementieren (und immer noch sehr nützlich) wäre, wäre:

const myFunc = () => ({ x: 10 });
type myType = returnof myFunc;    // { x: number; }

Theoretisch könnten sie auch verkettet werden, wenn Sie den Typ ein paar Ebenen tief erhalten möchten. Irgendwelche Gedanken?

Bearbeiten: Habe gerade festgestellt, dass dies oben von @mpawelski 😄 erwähnt wurde

@dehli Das würde jedoch nicht für überladene Funktionen funktionieren. Oder generische Funktionen, bei denen ein Typparameter im Rückgabetyp verwendet wird.

@JsonFreeman Konnten Funktionen nicht einfach OR die verschiedenen Rückgabetypen überladen? Und generische Funktionen könnten erfordern, dass Sie den Typ angeben?

Es wäre möglich, aber ich bin mir nicht sicher, wie nützlich das wäre. Es scheint, als wollten die Leute etwas Anspruchsvolleres.

Ich hoffe wirklich, dass dies bald umgesetzt wird, da es ein echter Schmerz ist.

Problemumgehung

Da der letzte Workaround nicht mehr funktioniert, verwende ich derzeit diesen:

// 0 argument function
export default function returnof<T>(fn: () => T): T;

// 1 argument function
// If no ambiguity simply infer the return type
export default function returnof<A, T>(fn: (a: A) => T): T;

// 1 argument function, with possible overload for the argument type.
// Explicitly set the type and use the correct overload
export default function returnof<A, T>(fn: (a: A) => T, a: A): T;

// ...

Sie müssen keine Argumente angeben, wenn Ihre Funktion nicht überladen ist.

const hello = (arg: number) => 'World'

const helloReturnValue = returnof(hello)
type helloReturnType = typeof helloReturnValue // string

Überladene Funktionen

declare function hello(): void;
declare function hello(a: number): number;

const helloReturnValue = returnof(hello)
type helloReturnType = typeof helloReturnValue // void

const helloReturnValue = returnof(hello, 42)
type helloReturnType = typeof helloReturnValue // number

Alle Definitionen sind hier:
https://github.com/kube/returnof/blob/master/lib/returnof.d.ts

Ich habe gerade ein kleines NPM-Paket erstellt , um es einfach zu bündeln, ohne Ihr Projekt zu verschmutzen.

Wenn optionale Generics wie in https://github.com/Microsoft/TypeScript/issues/2175 vorgeschlagen hinzugefügt werden, ermöglicht dies eine einfache Problemumgehung, die nur deklarativ ist:

type Return<T extends () => S, S> = S

Und benutze es so:

type helloReturnType = Return<typeof hello>

@mhegazy @JsonFreeman Irgendwelche Pläne für diese Funktion?

Hallo allerseits,
Ich habe eine alternative mögliche Lösung für dieses Problem (es tut mir leid, wenn sie bereits vorgeschlagen wurde und ich sie übersehen habe) - ich habe sie in Ausgabe Nr. 13949 vorgeschlagen. Ich wusste zu diesem Zeitpunkt noch nichts über die Art des Operators, aber ich denke, meine Lösung ist allgemeiner. Führen Sie im Grunde eine neue Syntax wie =MyType ein, die überall dort verwendet werden kann, wo Sie MyType verwenden würden, aber anstatt das Objekt als Typ MyType zu deklarieren, erstellt es eine Typ namens MyType aus dem abgeleiteten Typ des Objekts. So würde ich es zB beim Erstellen einer Vue-Komponente verwenden.

function createData(){
  return <=MyData>{
    dataProp1: <string>null
  }
}

function method1(){
  let self: MyComponent = this;
  console.log(self.dataProp1);
  self.method2();
}

function method2(){
  let self: MyComponent = this;
  //dostuff
}

type MyComponent = MyData & MyMethods;

let componentOptions = {
  data: createData,
  methods: <=MyMethods>{method1, method2}
}
//todo: register component...

Beachten Sie, dass die Funktion createData auch geschrieben werden könnte

function createData(): =MyData {
  return {
    dataProp1: <string>null
  }
}

Ich habe eine wirklich lustige Arbeit gefunden, die sich auf jeden Ausdruck verallgemeinern lässt:

const varWithRightType = (false as true) && some.deep.access()
type MyType = typeof varWithRightType;

BEARBEITEN: HÖREN SIE AUF, MICH ZU LACHEN, DAS IST ERNSTE SCHRIFTSCHREIBEN

@johnfn das sieht für einfache Fälle lustig aus :), aber wenn Ihre Funktion einige Parameter erfordert, müssen Sie wie folgt zusätzliche Arbeit leisten:

const stateProps = (false as true) && mapStateToProps({} as any);

Ich verwende eine andere Lösung, die besonders nützlich für React & Redux ist, wenn ich versuche, Typinferenz zu verwenden, um den Requisitentyp aus der mapStateToProps -Funktion zu erhalten. Dann besteht keine Notwendigkeit mehr, die Schnittstelle von Props, die von Redux connect injiziert wurden, manuell zu deklarieren und zu warten, was mühsam zu warten und fehleranfällig ist.
Diese Problemumgehung wird auch jede gegebene Funktionssignatur für andere Anwendungsfälle gut handhaben.

Beispiel für den typeof -Operator „React & Redux“-Anwendungsfall:

Das folgende Beispiel versucht, einen allgemeinen Anwendungsfall aus einem realen Projekt für die Verwendung des typeof -Operators zu zeigen, um einen Typ von einer Funktionsdeklaration abzuleiten, die äußerst nützlich wäre, wenn sie zu TypeScript hinzugefügt würde.

import { returntypeof } from 'react-redux-typescript';
import { RootState } from '../../store';
...

const mapStateToProps = (state: RootState) => ({
  currencies: CurrencyRatesSelectors.getCurrencies(state),
  currencyConverter: storeState.currencyConverter,
});

const stateProps = returntypeof(mapStateToProps); 
type Props = typeof stateProps & typeof dispatchToProps;
type State = {};

class CurrencyConverterContainer extends React.Component<Props, State> {
...

Quelle: https://github.com/piotrwitek/react-redux-typescript-patterns#react -connected-components

Die Lösung von @kube und ihr Paket https://github.com/kube/returnof scheinen wie erwartet zu funktionieren! 👍

Hm. Ich wünschte, returnof würde helfen, tupelbasiertes map() aus einer .d.ts -Datei einzugeben, aber angesichts seiner Abhängigkeit von der Ausdruckssprache ( const nicht in Umgebungskontexten verwendbar ), befürchte ich, dass mein Anwendungsfall damit zumindest komplizierter wäre.

Bearbeiten: Scheinbar sind optionale Generika inzwischen zusammengeführt , was bedeutet, dass die deklarative Sprachversion von @kube ( type Return<T extends () => S, S> = S -> type helloReturnType = Return<typeof hello> ) realisierbar wäre. :D

Korrektur: nein, die zusammengeführte Unterstützung für generische Standardwerte scheint es nicht zu ermöglichen, einen Teil der generischen Werte anzugeben, während andere auf ihre abgeleiteten Werte zurückgreifen können; Return<typeof hello> liefert nur den Fehler Generic type 'Return' requires 2 type argument(s). .

@tycho01 Ja, ich war enttäuscht, dass es das Problem nicht gelöst hat.

Ich habe bereits ein verwandtes Problem zu Optional Generic Types Inference geöffnet:

14400

Sie können den Rückgabetyp einer Funktion im Allgemeinen über eine Kombination aus einer Dummy-Ambient-Funktion und einer Typinferenz erhalten:

Umgebung function returnTypeOf<RT>(fn:(...rest:any[])=>RT):RT {return void 0};
lokal erhält var r = returnTypeOf(someFunction); den Wert undefined

Aber wenn Sie diesen Rückgabetyp dann wiederverwenden möchten, müssen Sie ihn erfassen ... also sind wir wieder da, wo wir angefangen haben:

type RT = typeof r;

Es wäre so viel einfacher, wenn wir zunächst das Konzept von typeof erweitern würden, um returntypeof zuzulassen, oder noch besser, diese Verwendung von typeof fn(a,b,c,...) ableiten, was dann verschiedene Signaturrückgabetypen erfassen könnte . Dieses Verstehen vom Rückgabetyp wird bereits von TS intern durchgeführt.

typeofexpression () wäre eine Art Rückgabetyprekursion gemischt mit Typoperationen: zB

type E = typeofexpression (f(1) + g("x"))

ist

type E = typecomprehensionof (typeof f(1) + typeof g("x"))

die wie eine von verstanden werden könnte

type E = typecomprehensionof (string + string) dh string
type E = typecomprehensionof (string + number) dh string
type E = typecomprehensionof (number + number) dh number

Wie schwer das intern ist und die Leistungskosten sind mir unbekannt.

BEARBEITEN ------------------------------------------------- ------------

Ich wollte hinzufügen ... dies ist besonders wichtig für alle, die Function.bind, Function.apply, Function.call verwenden müssen, da diese derzeit den Typ any zurückgeben und dies bedeutet, dass sie ständig typisiert werden müssen. kommentiert, um sicherzustellen, dass sie nicht aus dem Typprüfungsprozess herausfallen.

In der Lage zu sein, auf den Rückgabetyp des Funktionsarguments zu verweisen, wäre ... Glückseligkeit ...

@poseidonCore

In der Lage zu sein, auf den Rückgabetyp des Funktionsarguments zu verweisen, wäre ... Glückseligkeit ...

Mir gefällt das aktuelle typeof <expression> besser, und das Problem mit dem Rückgabetyp wird bereits in #12342 behandelt.

Ich habe nur typeofexpression und typecomprehensionof verwendet, um diese Verwendungen in der Erklärung zu unterscheiden. Ich würde typeof (expression) als eigentliche Syntax bevorzugen.

Mein Punkt war, dass das Verständnis des Typs eines Ausdrucks das Verständnis des Rückgabetyps einer Funktion erfordern würde ... f(1) ist auch ein Ausdruck. #12342 ist auf diese Weise verwandt. Sie schließen sich nicht gegenseitig aus.

Wir können bereits typeof für Variablen ausführen, und da ein Ausdruck eine Operation über Variablen und Funktionen ist, besteht die nächste Anforderung darin, den Typ einer Funktion zurückzugeben ... und dann das Ergebnis basierend auf Typregeln zu verstehen .

Eigentlich guter Punkt. Das Problem Nr. 12342 ist eine Möglichkeit, auf den Rückgabetyp als eine Art Eigenschaft für generische Typen zuzugreifen, daher habe ich die Beziehung falsch verstanden.

Wie wäre es mit Ausdrücken für den Aufruf der Funktion typeof wie im Vorschlag, aber mit Typen direkt anstelle von Parametern?

Z.B

function myFunction<T>(param1: T, param2: string, param3: number): T & {test: string} {
  // ...
}

type ResultType = typeof myFunction({hello: string}, string, number)
// ResultType is: {hello: string} & {test: string}

Beachten Sie, dass dies niemanden daran hindert, Typen von Variablen mit lokalem Gültigkeitsbereich zu verwenden, indem Sie typeof innerhalb der Aufrufe verwenden, dh:

type ResultType = typeof myFunction(typeof obj, string, number)
// ResultType is: typeof obj & {test: string} 

Dies scheint etwas besser zu sein als der ursprüngliche Vorschlag, da es in einem Umgebungskontext funktioniert und im Allgemeinen flexibler erscheint. Für mich macht es auch klarer, dass wir die Funktion nicht wirklich aufrufen, sondern nur versuchen, den Typ ihres Rückgabewerts zurückzugeben.

Wie ich es für meinen einfachen Fall gemacht habe:

interface IAlertMessage {
  addedAt: number;
  text: string;
  type: "error" | "warning" | "info" | "success";
}

declare let alertMessageInterface: IAlertMessage;

const messages: IAlertMessage[] = [];

function addMessage(text: string, type: typeof alertMessageInterface.type): void {
  messages.push({addedAt: new Date().getTime(), text, type});
}

addMessage("something", "info"); // <- OK - and also has intellisense for the second parameter (after typing first " of the second parameter, press Ctrl+Space in VSCode)
addMessage("something", "fatal"); // <- Compilation error : error TS2345: Argument of type '"fatal"' is not assignable to parameter of type '"error" | "warning" | "info" | "success"'.

Das obige funktioniert auch mit Mitgliedern der Schnittstelle selbst:

declare let myIface: MyInterface;

interface MyInterface {
    prop1: {
        big: {
            complex: {
                anonymous: { type: {} }
            }
        }
    },
    prop2: typeof myIface.prop1.big.complex.anonymous;
}

Der Vorteil der Verwendung von declare let ... anstelle von type ... besteht für mich darin, dass in der .js-Ausgabedatei nichts erzeugt wird.

@varadero löst das vorliegende Problem nicht, nämlich den Rückgabetyp eines Funktionsaufrufs abzurufen. Alles, was Sie tun, ist, den Typ einer Eigenschaft eines Typs abzurufen, was meines Erachtens seit TypeScript 2.2 eine unterstützte Funktion ist.

Das ist eigentlich der Typ einer Eigenschaft eines (falschen) Wertes. Der Compiler lässt Sie typeof nicht mit "reinem" Typ oder Interface verwenden, er funktioniert nur mit Werten.

Das wäre toll zu haben!

da type KEYOF<T extends any[]> = keyof T[0] bereits existiert, würde typeof T[0]('something') auch mit dieser Implementierung funktionieren, vorausgesetzt, function myFunc<T, K extends KEYOF<T>>(type: K)>{ } und myFunc([(r: string) => r]) (würde einen String für typeof T[0]('something') zurückgeben) ?

Dies wäre zusammen mit polymorphem this mächtig.

Zeit dafür!

Beim erneuten Lesen dieses Threads habe ich versucht zu verstehen, was wir syntaktisch mit typeof machen und warum.

Offensichtlich gibt es im bereits implementierten trivialen typeof foo -Fall den Typ von foo an. (Ich stelle mir vor, dass pet is Fish gegen die Verwendung einer speziellen Syntax immun ist.)

In meiner aktuellen Lektüre dessen, was dieses Schlüsselwort im aktuellen Vorschlag tun sollte, macht das Schlüsselwort selbst nicht mehr als das, was es jetzt tut, und der aktuelle Vorschlag hat tatsächlich nichts mit dem typeof -Schlüsselwort zu tun.

Ich spreche das an, weil es in dem oben erwähnten Fall von Bedeutung ist, in dem wir eine Funktion anwenden, die als Typ gespeichert ist (sei es durch type oder als generisch), und nicht als Wert.

Angesichts dessen nehme ich an, dass, während typeof fn<A>(string) typeof benötigt, um die Variable fn auf Ausdrucksebene für die Verwendung auf Typebene anzuheben, Fn<A>(string) auf der anderen Seite , mit Fn als Generikum, das eine Funktion enthält, würde dies nicht erfordern und könnte daher "angewendet" werden, um hier den entsprechenden Rückgabetyp zu erhalten, ohne dass typeof benötigt wird.

In dieser Interpretation würden wir nach Funktionsaufrufen nach möglichen Funktionstypen suchen: neben typeof fn(...) / typeof fn<...>(...) auch Fn(...) / Fn<...>(...) , wenn nicht sogar funktionieren wörtlich ((foo: Foo) => Bar)(Baz) / + Generika. Ansonsten sollte der Angriffsplan intakt bleiben.

Vielleicht interpretiere ich falsch, wie Sie das sehen, vielleicht wurden Funktionen, die in Typen gespeichert sind, nicht einmal berücksichtigt (da ich keine Erwähnung von ihnen gefunden habe). Wie auch immer, ich dachte, es wäre eine Bestätigung wert.

Wenn das Ankündigen der Funktionsanwendung zu einer weiteren semantischen Überlastung für typeof wird, zusätzlich zur Disambiguierung von Klassenkonstruktoren und dem Heben von Variablen auf Ausdrucksebene auf die Typebene (abgesehen von typeof Ausdrucksebene ), Dinge scheinen allmählich verworrener, wie bereits einige frühere Fragen in diesem Thread angedeutet haben.

Bearbeiten: Ein generischer Typ kann bereits einen Funktionstyp zurückgeben, der auch Generika haben könnte. Dies bedeutet, dass eine andere Permutation GetFn<A><B>() wäre, wobei die erste generische Menge zum generischen Typaufruf gehört, letztere zum Funktionsaufruf gehört. Keine GetFn<A><B><C>() , obwohl GetFn<A><B>()<C>() auch legitim wären. Die frühere Schlussfolgerung bleibt jedoch unverändert: Jeder Typ kann eine Funktion enthalten und daher (potenziell) als Funktion angewendet werden.

Bearbeiten 2: Mir ist gerade aufgefallen, dass es in X<Y>() eine unglückliche Mehrdeutigkeit geben würde. Jetzt wäre X ein Typ, der einen Funktionstyp zurückgibt.

  • Wenn X nicht generisch ist, ist dies klar – <Y> gehört zum Funktionsaufruf.
  • Wenn X generisch ist und den Parameter benötigt, ist dies ebenfalls klar – es gehört zum Typ, und der Funktion werden keine Typparameter übergeben.
  • Wenn X jedoch ein optionales Generikum hat, wird dies mehrdeutig.

    • In einer Interpretation wird X ein Typparameter übergeben, die Funktion jedoch nicht.

    • In einer anderen Interpretation wird X überlassen, seinen Standardtyp param zu verwenden, während <Y> stattdessen den Funktionsaufruf parametrisieren würde.

Ich bin mir noch nicht sicher, was hier die beste Lösung wäre.

  • Eine Option wäre, Typaufrufe zu erzwingen, die sich vollständig auf Standardtypparameter verlassen, um explizit das leere <> zu verwenden, anstatt auch zuzulassen, dass dies übersprungen wird. Dies wäre jedoch eine bahnbrechende Änderung.
  • Eine Alternative wäre, stattdessen eine solche Einschränkung für den Funktionsaufruf auf Typebene aufzuerlegen – da dies neu wäre, würde keine Breaking Change eingeführt. Dieser Ansatz wäre jedoch insofern unelegant, als er eine Asymmetrie zwischen der Syntax für den Funktionsaufruf zwischen den Ausdrucks- und Typsprachen einführen würde, was ihn weniger intuitiv machen könnte.

@icholy : Ja, diese Änderungen tragen nur weiter zu Ihrem Standpunkt bei, ich weiß.

Edit 3: Okay, dieses Feature steht jetzt ganz oben auf meiner Wunschliste. Kein anderes Upgrade kommt auch nur annähernd in Bezug auf die Auswirkungen auf die Inferenz.

wat

@icholy : Kurz gesagt, wenn die 'Funktion' in einem Generikum/Typ ist, würden wir dann immer noch typeof schreiben?

Wenn dies getan wird, damit es korrekt mit Überladungen interagiert, erhalten Sie tatsächlich "variadische" Funktionen kostenlos (sie sind nur Curry, anstatt > 1 Stelligkeit zu haben), da Sie den Rückgabetyp einer Funktion in Bezug auf rekursiv definieren können Anwendung einer seiner Überladungen mit einer gewissen Basisfallüberladung. So werden die Dinge in Haskell gemacht, und es wäre eine wunderbare Funktion, die man in TypeScript haben könnte.

@masaeedu : das klingt interessant. Und ja, Überladungen machen diesen Vorschlag definitiv so interessant – sie könnten verwendet werden, um Muster über verschiedene Optionen hinweg abzugleichen, einschließlich any Fallbacks. Solche Typprüfungen waren bisher auf Typebene noch nicht möglich.

Ich gebe zu, dass ich Ramda mehr benutzt habe als Haskell. Aber vor diesem Hintergrund dachte ich, dass sich Currying normalerweise nicht gut mit variadischen Funktionen kombinieren lässt, da Curry „wissen“ müsste, ob das Ergebnis oder eine andere Funktion zurückgegeben werden soll, um mit zusätzlichen Argumenten umzugehen.

Könnten Sie vielleicht etwas Pseudocode zeigen, wie diese Idee für unterschiedliche Funktionen wie Object.assign funktionieren würde (wobei Details wie & im Vergleich zu $# Overwrite 3$#$ übersprungen werden, wie ich es verwendet habe in meinem PoC für R.mergeAll )?

@tycho01 Ich habe mit Code wie diesem herumgespielt:

interface Pop<TVarStack extends VarStack> {
    (a: TVarStack["head"]): Pop<TVarStack["tail"]>
}

interface VarStack<THead = any, TTail extends (void | VarStack) = any> {
    head: THead
    tail: TTail
    <TNew>(a: TNew): VarStack<TNew, this>
    (): (a: Pop<this>) => any
}

// Figure out implementation later :)
let stack: VarStack<void, void>;
const pop = stack(new Date())(10)("Bob's")("your")("uncle")()

// a, b, c, d, and e are well-typed
pop(a => b => c => d => e => {
    // Need this because can't figure out how to encode base-case in Pop recursion
    return stack
})

VarStack ist in gewissem Sinne ein "variadischer" Funktionstyp, da Sie ihm eine beliebige Anzahl heterogener Argumente geben können und die zugehörigen Typinformationen mithilfe von Rekursion auf Typebene genau aufzeichnen. Leider bietet TypeScript keine gute Unterstützung für Transformation und Musterabgleich auf Typebene, ebenso wie Haskell.

Wenn wir Zugriff auf typeof hätten, wäre ich in der Lage, das Basisfallproblem für Pop zu lösen, da returnof(overloadedFunction(T)) mir im Wesentlichen eine Möglichkeit geben würde, Muster zu erstellen. Abgleich auf Typebene.

@tycho01 Ich kann es nicht genau für Object.assign tun, da dies wie gesagt nur für Curry-Funktionen funktioniert, aber ich werde versuchen, eine Curry-Funktion für assign für Sie zu erstellen, die ungefähr funktioniert in der gleichen Weise.

Ich wünschte, sie würden die Typenalgebra ein wenig verallgemeinern, so dass zB (a: string) => (b: number) => void nur Zucker für Func<string, Func<number, void>> und (a: string, b: number) => void nur Zucker für Arity<number, Arity<string, void>> war, oder sowas in der Art. Dann bräuchten wir nur Allzweckwerkzeuge zum Transformieren von Typen, um viele interessante Funktionen zu erhalten.

Entschuldigt alle für den dreifachen Post. @tycho01 Hier ist Curry, "variadisch" assign :

interface Assign<TCurr extends {} = {}> {
    <TAdd extends {}>(a: TAdd): Assign<TCurr & TAdd>
    (): TCurr
}

let assign: Assign = undefined // implement somehow
const result = assign({ foo: "bar" })({ baz: 42 })()

@masaeedu : Hah, also denke ich, es ist wie eine Reduzierfunktion :), obwohl dieser Teil vielleicht schwieriger werden könnte, wenn es auch nicht-variadische (?) Argumente gäbe.

Ich mag die Idee; In Richtung Schnittstellen habe ich definitiv nicht so viel gedacht.
Wenn wir jedoch das JS für die Typisierungen ändern können, werde ich vielleicht TC39 bitten, einfach Object.assign in ein nicht-variadisches mergeAll zu ändern. :D

Nicht wie meine inkrementbasierte Iteration, die bisher tatsächlich mit Funktionen in der Praxis funktioniert hat (#17086) ... aber so oder so bin ich bei Ihnen, dieser Vorschlag hätte eine größere Wirkung als jeder andere, den ich gesehen habe.

@tycho01 Ich denke, Sie werden wahrscheinlich nicht viel Anklang finden, wenn Sie verlangen, dass variadische Funktionen in Funktionen mit fester Arität umgewandelt werden, da Curry-Funktionen oder wiederholte Anwendungen, die JS (oder zumindest vorhandene JS-Engines und Code) ausführen, Laufzeitkosten verursachen. ist wahrscheinlich nicht hoch optimiert für. Ich denke, was wir stattdessen brauchen, ist, dass TypeScript es gleichermaßen einfach macht, Typen für arity > 1-Funktionen rekursiv aufzubauen und zu dekonstruieren. Ich denke, das kommt alles auf # 5453 zurück.

Andere finden es vielleicht interessant zu erfahren, dass C++ ein sehr ähnliches Feature hat: decltype(expression)

Wird diese Funktion es jemals zu TypeScript schaffen?
Ich bevorzuge die ursprünglich vorgeschlagene Form, in der das Argument ein Ausdruck ist, aber die Absicht des TS-Teams, eine spezielle Syntax mit Typen in Ausdruckspositionen zu haben, kompliziert alles und verzögert die Landung in der Sprache.
Wir müssen dieses Thema weiterführen.

Ich wäre wirklich froh, wenn wir auch nur einen Teil dieses Vorschlags umsetzen könnten. Nämlich diese eine:
type A = typeof anotherVariable . Ich könnte alles mit nur dieser einen Zeile machen. interface B extends A , <B & A> : B & A usw.

Ich sehe viele Anwendungsfälle für diese Art von Dingen mit React. Wenn Sie eine Komponente höherer Ordnung erstellen, ist es viel schwieriger, der Klassendeklaration der Komponente anzudeuten, dass es sich derzeit um eine HOC handelt.

Am Ende habe ich zwei Klassen erstellt. Eine Klasse erweitert React.Component und fügt alle zusätzlichen Methoden hinzu, während die Klasse innerhalb der HOC-Funktion die render() manipuliert.

@blindbox type A = typeof anotherVariable funktioniert bereits:

var data = [1, 2, 3].map(v => ({ raw: v, square: v * v }));

function checkItem(item: typeof data) {
    // item is Array<{ raw: number; square: number; }>
}

@Igorbek Wow, du hast recht.

Okay, ich habe herausgefunden, warum es bei mir nicht funktioniert hat.
Das wird nicht funktionieren

interface B {
  thisIsB: boolean
}

const typeTest = (type: any) => {
  return 1 as any as App & B
}
type A = typeof typeTest(1)
declare const aVal: A; // aVal's type isn't of type App & B, but something else.

Dies wird jedoch funktionieren.

interface B {
  thisIsB: boolean
}

const typeTest = (type: any) => {
  return 1 as any as App & B
}
const typeTestVal = typeTest(1)
type A = typeof typeTestVal
declare const aVal: A; // aVal's type is of type App & B!

Ja, das Problem ist, dass es in bestimmten Anwendungsfällen nicht verwendet werden kann, z. B. wenn Sie generische Typargumente verwenden müssen.

function someFunctionWithComplexReturnTypeThatUsesGenerics<T>(item: T) { ... }

// it is impossible to work this around because there's no chance to create a fake variable that would be parameterized by T
function use<T>(item: typeof someFunctionWithComplexReturnTypeThatUsesGenerics<T>(item)) { ... }

@Igorbek :

Ich bevorzuge die ursprünglich vorgeschlagene Form, in der das Argument ein Ausdruck ist, aber die Absicht des TS-Teams, eine spezielle Syntax mit Typen in Ausdruckspositionen zu haben, kompliziert alles und verzögert die Landung in der Sprache.

Seit sie das geschrieben haben, haben wir Literale für Strings und Zahlen bekommen, was bedeutet, dass die Dinge etwas unschärfer werden – diese würden so oder so funktionieren. Literale Arrays und Objekte sehen in ihren Typnotationen ähnlich aus, so weit, so gut.

Dann haben wir also auch gespeicherte Werte/Typen, zB Variablen, Generika, andere Typen. Damit die Dinge nützlich werden, sollten wir uns hier nicht nur mit einem Teil davon begnügen müssen.
Was hier ein gutes Argument für ihren typbasierten Ansatz ist, ist, dass das Zulassen dieser Mischung auf Typebene kostenlos ist. Das heißt, auf der Typebene könnten wir bereits typeof myVar sagen, was bedeutet, dass wir, wenn diese Funktion „Anwendung“ zur Typebene hinzugefügt wird, automatisch in der Lage wären, sowohl gespeicherte Typen als auch reguläre Variablen einzufügen .

Ich würde gerne Ihre weiteren Gedanken zu dem ursprünglich vorgeschlagenen Ansatz sehen. Zugegeben, das könnte dazu dienen, die Typebene etwas näher zu beleuchten: JS-basierte Operatoren (denken Sie an ! && || + - * / instanceof ) sowie TypeScript-spezifische Operatoren (Assertionsoperator ! ).
Die Sache mit diesen JS-Operatoren ist wie ... sie sind auf der Typebene in ihrer jetzigen Form ziemlich nutzlos, da es derzeit als außerhalb des Gültigkeitsbereichs ( ref ) - expression gilt, wenn sie es ihnen erlauben, mit Literalen zu operieren, um die entsprechenden Literal-Ergebnistypen zu liefern -level 1 + 1 ergibt nur den Typ number und ähnliches für die anderen.
Vor diesem Hintergrund war ich mit ihrem typenbasierten Vorschlag selbst irgendwie unzufrieden.

Wird diese Funktion es jemals zu TypeScript schaffen? [...] Wir müssen dieses Thema weiterführen.

Ich habe diesen Vorschlag als allgemeinere Lösung für ein kleineres Symptom hier vorgeschlagen, wenn auch mit begrenztem Erfolg.

@tycho01 Meine Argumente für die ausdrucksbasierte Variation von typeof sind fast die gleichen wie ursprünglich von @yortus angegeben:

  • Konsistenz : Das vorhandene typeof (in der Schreibposition) arbeitet bereits mit Ausdrücken, ist jedoch darauf beschränkt, ein einzelnes Symbol zu akzeptieren. Das Akzeptieren von Typen oder Pseudoaufrufen würde also eine viel umständlichere Syntax einführen, die viel mehr ein weiterer Syntaxzweig ist (zusätzlich zu Ausdrücken und Typen).

    • Benutzer müssen keine neue Syntax lernen

  • Einfachheit : TypeScript scheint bereits über alle Einrichtungen zu verfügen, die erforderlich sind, um diese Funktion auf unaufdringliche Weise zu implementieren (es wurde von der PR bewiesen).
  • Vollständigkeit : Ausdrücke haben zumindest die gleiche Aussagekraft wie Typen, da wir immer Typzusicherungen ( (undefined as any as <any arbitrary type can be here>) ) verwenden können, aber manchmal fehlen dem Typsystem Typen, die jedoch in Ausdrücken ausgedrückt werden können (Spread- und Rest-Typen wurden später eingeführt die entsprechenden Ausdrücke waren gelandet).

Danke @tycho01 , dass du diesen Punkt zu promised PR gebracht hast (ich bin tatsächlich nach deinen Kommentaren hierher gekommen), das zeigt wirklich, wie eine so einfache und generische Funktion viel komplexere Szenarien auf sehr elegante Weise abdecken kann, ohne zu backen in ganz bestimmtem Verhalten in die Sprache.

Ich sehe das erweiterte typeof als einen echten Spielwechsler für die Ausdruckskraft von Typsystemen, ähnlich wie gemappte Typen/ keyof taten dies.

@Igorbek : Danke für die Erläuterung, ich sehe, woher du kommst. Vielleicht dienen diese beiden Ansätze dann unterschiedlichen Anwendungsfällen.

Ich denke, Sie demonstrieren den heutigen Wert des ursprünglichen Vorschlags besser als der ursprüngliche Post heute, da der aktuelle TS (oder, nun ja, Playgrounds 2.3.3 ) viele der ursprünglichen Szenarien bereits zum Laufen bringen kann:

// I have a strongly-typed collection but the element type is anonymous/unknown, how can I reference the element type? (#3749)

// Mapping to a complex anonymous type. How to reference the element type?
var data = [1, 2, 3].map(v => ({ raw: v, square: v * v }));
declare function checkItem(item: typeof data[0]): any
// ^ no longer errors, needs no further change

// A statically-typed dictionary. How to reference a property type?
var things = { 'thing-1': 'baz', 'thing-2': 42 };
type Thing2Type = typeof things['thing-2'];
// ^ no longer errors, needs no further change

// A strongly-typed collection with special indexer syntax. How to reference the element type?
var nodes = document.getElementsByTagName('li');
type ItemType = typeof nodes.item(0);
// ^ the `.item` access works, but function applications still errors, would be fixed by either version of the proposal.

// A function returns a local/anonymous/inaccessible type, how can I reference this return type? (#4233, #6179, #6239)

// A factory function that returns an instance of a local class
function myAPIFactory($http: HttpSvc, id: number) {
  class MyAPI {
    constructor(http) {}
    foo() {}
    bar() {}
    static id = id;
  }
  return new MyAPI($http);
}
declare function augmentAPI(api: typeof myAPIFactory(HttpSvc, number) /* ERROR */): any
// ^ function applications errors, would be fixed by either version of the proposal, if using slightly different syntax.

// I have an interface with a complex anonymous shape, how can I refer to the types of its properties and sub- properties ? (#4555, #4640)

// Declare an interface DRY-ly and without introducing extra type names
type MyInterface = {
  prop1: {
    big: {
      complex: {
        anonymous: { type: {} }
      }
    }
  },
  // prop2 shares some structure with prop1
  prop2: MyInterface['prop1']['big']['complex'];
}
// ^ no longer errors after swapping `.k` access to `['k']`. `typeof` was unnecessary and counter-productive -- MyInterface isn't exposed on the expression level.

Ich denke, diese Beispiele sprechen nicht mehr für den Ausdrucks- oder den Typansatz - die meisten sind veraltet, und die Anwendung trivialer Funktionen würde durch beide ermöglicht.

Zugegebenermaßen kann eine auf Typebene basierende Funktionsanwendung, auf die sich tinganho konzentriert, das TS-Team und ich Ausdrucksvariablen über typeof verfügbar machen, obwohl, wie Sie bemerkt haben, die Typebene zugegebenermaßen hinter der Funktionalität zurückbleibt, die für den Ausdruck verfügbar gemacht wird Stufe. Diese Funktionsanwendung selbst sowie die von Ihnen erwähnte Spread-Syntax sind offensichtlich primäre Beispiele, und ich würde auf jeden Fall gerne sehen, dass sie angesprochen werden. Vielleicht könnte ein Großteil der derzeitigen Lücke sogar hier angegangen werden.

In ähnlicher Weise würde ein Expression-First-Ansatz auch das Einfügen von Typen mit <MyType> whatever oder whatever as MyType ermöglichen, obwohl es als typeof im Typ-Ansatz ähnlich wie ein nachträglicher Einfall erscheinen würde: Fragen über die Anwendung von Funktionen, die in Typen/Generika gespeichert sind, verbleiben, die wahrscheinlich den größten Teil des tatsächlichen Mehrwerts dieses typbasierten Ansatzes ausmachen (obwohl dort nicht erwähnt) - genaue Rückschlüsse auf Funktionen höherer Ordnung sowie beispielsweise typbasierte Bedingungen wie in diesem promised -Thread.* Schlimmer noch, im Gegensatz zum Anwendungsfall des OP haben diese keine Problemumgehungen.

Ich denke, sehen Sie, wo die Ideen kollidieren - aktuelle Vorschläge haben widersprüchliche Ansichten darüber, ob das Schlüsselwort typeof seinen Ausdruck auf die Wertebene ändern würde, vs. (in meiner Interpretation) typeof so zu lassen, wie es ist , aber die Funktionsanwendungssyntax auf der Typebene verfügbar machen.

Meiner Ansicht nach ist der Widerspruch jedoch eher zufällig. Ich würde die Legitimität beider Anwendungsfälle nicht außer Acht lassen; Wenn beide implementiert werden würden, könnte ich ein zusätzliches Schlüsselwort sehen, das einen Semantikkonflikt vermeidet. Es ist mir ehrlich gesagt gleichgültig, ob Keywords auf die eine oder andere Weise enden – ich will nur Scheiße tippen.

*: Mir ist gerade klar geworden, dass Sie Funktionen höherer Ordnung wahrscheinlich angehen würden, indem Sie Funktionen in Parametern anhand ihrer Parameternamen referenzieren, anstatt ihre Typen in Generika zu erfassen.
Es sieht so aus, als könnten die Dinge tatsächlich in beide Richtungen umgewandelt werden: typeof $ hilft dabei, Dinge von der Wertebene auf die Typebene zu heben, während declare let y: x; dabei hilft, Dinge von der Wertebene auf die Typebene zu heben. Unelegant wie die Problemumgehung des OP, aber ja.
Wenn Funktionstypen zB durch explizite Generika eingeführt würden, würde dies meiner Meinung nach nicht helfen - sie würden sich auf der Typebene befinden, ohne dass eine Möglichkeit besteht, sie zu verschieben.
Wenn das so klingt, als würde ich vorsorglich hoffen, die Grundlagen der Funktionalität abzudecken, liegt das wahrscheinlich daran, dass ein Großteil meiner Fortschritte bei ungelösten Tippherausforderungen für dieses eine Add-On blockiert wurde (mit bemerkenswerter Erwähnung von 5453). Aber wenn wir diese Dinge herausfinden können, wird es sich lohnen.

Bearbeiten: Ich habe an einige theoretische Fälle gedacht, die jetzt in einem ausdrucksbasierten Ansatz ausgeschlossen werden könnten, obwohl mir noch keine praktischen Vorkommnisse eingefallen sind:

  • Funktionen, die über ein explizit bereitgestelltes Generikum übergeben werden.
  • Funktionen, die von Parameterfunktionen zurückgegeben werden, in einem generischen zu erfassen, dh (unter Verwendung der Syntax des ausdrucksbasierten Vorschlags) declare function f<G extends (...args: any[]) => R, R extends <T>(foo: T) => Bar<T>>(g: G): typeof R(baz); // error following the expression-based proposal: R is not exposed at the expression level . Diese könnten natürlich berechnet werden, wenn Sie die Parametertypen von G kennen und bereitstellen könnten, aber leider gibt es bisher keine Möglichkeit, diese Informationen zu erhalten (außer vielleicht #14400?). Ich nehme an, diese Kategorie würde Funktionen umfassen, die mit den oben erwähnten Fabrikfunktionen arbeiten, die in AngularJS verwendet werden.

@Igorbek Ich verstehe nicht, was Sie mit diesem Snippet erwarten:

function use<T>(item: typeof someFunctionWithComplexReturnTypeThatUsesGenerics<T>(item)) { ... }

Es scheint eine zirkuläre Definition zu sein.

@masaeedu Entschuldigung, ich meinte:

(Wechseln Sie zu f anstelle von someFunctionWithComplexReturnTypeThatUsesGenerics )

function use<T>(item: typeof f<T>(undefined as any as T)) { ... }

Nur um das näher auszuführen, das kann derzeit nicht durch die Einführung einer gefälschten Variable umgangen werden:

const _typeofItem = f<T>(undefined as any as T); // no T here
function use<T>(item: typeof _typeofItem) { ... }

Übrigens, ich habe gerade festgestellt, dass der aktuelle Vorschlag mit dem Elementtyp-Abfragetyp-Operator T[K] in Konflikt steht, da typeof eine höhere Priorität hat:

  • typeof x[K] bedeutet derzeit (typeof x)[K] , wobei K _is_ ein Typ ist
  • Wenn die ausdrucksbasierte Variation verwendet wird, würde eine Mehrdeutigkeit eingeführt:

    • typeof x[k] bedeutet (typeof x)[k] wobei k ein Typ ist; oder

    • typeof x[k] bedeutet typeof (x[k]) wobei k ein Ausdruck ist

Ich würde eigentlich typeof (x[k]) bevorzugen, weil sie semantisch äquivalent sind, aber es ist sicher eine bahnbrechende Änderung.

Als ich die Entwicklertools von Chrome verwendet habe, habe ich festgestellt, dass der Member-Operator eine höhere Priorität hat. Wo hast du das gefunden?

image

@Igorbek : Ja, das scheint der Grund dafür zu sein, dass type Thing2Type = typeof things['thing-2']; aus dem ursprünglichen Beitrag bereits funktioniert.

@dehli : Sie vergleichen die JS-Ausdrucksebene mit der TS-Typebene - sie sind nicht gleich. Einer läuft im Browser/Knoten, der andere im TS-Compiler.

Ich persönlich finde es seltsam , dass TypeScript typeof mit einer höheren Priorität als indizierter Typzugriff auflöst. Für mich sieht es ehrlich gesagt fast wie ein Bug aus. (Ich frage mich, ob dieser Fall tatsächlich getestet wird.)

@tycho01 Erwischt . Ich nahm an, dass TS die gleiche Rangfolge wie JS verwenden würde.

@dehli die Ausdrucksstufen sind gleich, ja. Auf der Typebene ist es eine andere Sache mit ähnlicher Syntax, die jedoch einen Typ anstelle einer Zeichenfolge zurückgibt.

Ich vermute, der Vorrang könnte das Ergebnis der Einschränkungen sein, die sie sich vorgestellt hatten. Ich gebe zu, dass diese Überlegungen möglicherweise nicht mehr so ​​​​sinnvoll erscheinen, wenn seine Funktion erweitert werden soll.

Auf einer anderen Anmerkung, wenn beide Versionen des Vorschlags umgesetzt werden sollten, würden die Klammern als wirksames Mittel dienen, um deutlich zu machen, auf welcher Ebene wir uns auch dafür befinden würden. Ich habe Probleme, Dinge zu finden, die nicht nach Kompromissen klingen, aber ja.

@Igorbek Ist dies der primäre verbleibende Anwendungsfall für typeof bei beliebigen Ausdrücken? Wenn wir #14400 bekommen, würde uns das returnof geben, wenn wir einfache benutzerdefinierte Typen verwenden, wenn ich mich nicht irre.

14400 gibt uns nicht returnof in seiner generischen Form:

type Return<T extends () => R, R = any> = R;

// overloaded function
declare function f(item: number): number;
declare function f(item: string): boolean;

type fType1 = Return<typeof f>; // ??
type fType2 = typeof f(1); // number
type fType3 = typeof f('a'); // boolean

// generic function
declare function g<T>(item: T): T;

type gType1 = Return<typeof g>; // ??
type gType2 = Return<typeof g<number>>; // not supported syntax
type gType3 = typeof g(1); // number
type gType4 = typeof g<number>(1); // number
type gType5 = typeof g('a' as 'a'); // 'a'

Mir sind nicht alle verbleibenden Anwendungsfälle bekannt, sie scheinen noch nicht entdeckt worden zu sein, aber die unschlagbarsten und attraktivsten für mich sind:

  • _bedingt zugeordnete Typen_, die viel mächtiger sind, als wir mit #12424 bekommen könnten, weil typeof die Standardüberladungsauflösung verwenden würde; @tycho01 hat ein elegantes Beispiel in promised Types PR #17077 gezeigt
  • Arten von Funktions-/Methodenrückgaben im Kontext generischer Typen erhalten (wie ich zuvor gezeigt habe)

@masaeedu : Gute Frage. Die Kurzversion ist so ziemlich das, was @Igorbek gesagt hat; Für ein bisschen mehr Details zu gelösten konkreten Herausforderungen finden Sie eine kurze Liste in meiner Liste der „Top Features Needs“ in #16392, wo ich versucht habe, ungelöste Herausforderungen mit den Vorschlägen zu verknüpfen, die sie ermöglichen könnten.

Das beinhaltet:

  • Factory-Funktionen, wie sie zB von AngularJS verwendet werden, siehe die drei Threads, die hier im Originalpost verlinkt sind. - Realistischerweise sollte dies angesichts des neu eingebauten ReturnType in Ordnung sein.
  • Bei bekannten Eingaben können Sie plötzlich die genauen Rückgabetypen für Dinge wie reduce / map / filter / find berechnen – alles, was mit Iterationen und Prüfungen zu tun hat. stdlib.d.ts würden dort profitieren, ebenso wie FP-Bibliotheken wie Ramda/Lodash. In der Praxis sind möglicherweise nicht alle Eingaben bekannt (eher listenähnliche Arrays als Tupel), aber map - und filter -Operationen auf Objekten könnten auch besser getippt werden als nur Partial . Dies ist notwendig, um Bibliotheken wie Redux (siehe https://github.com/piotrwitek/react-redux-typescript/issues/1) und Angulars ngrx, die bis dahin gezwungen sind, ihre Benutzer-API auf niedrigem Niveau zu halten, besser Typzustandsverwaltungsbibliotheken zu geben denn ohne eine gut typisierte map -Operation gibt es für sie keine Möglichkeit, das gewünschte Ergebnis auf Typebene aus den DRY'er-Eingabedaten zu berechnen.
  • Auch Funktionskompositionstypisierungen sind derzeit nicht in der Lage, eingabeabhängige Rückgabetypen zu berücksichtigen.
  • Es würde plötzlich auch möglich sein, mit dem Tippen von Dingen wie Linsen zu beginnen. - Ich nehme an, technisch ist dies hier nur noch bei Bearbeitungsoperationen mit eingabeabhängigen Rückgabetypen blockiert. das ist wirklich ein Luxus. Das Ändern von Tupeln benötigt jedoch immer noch (einen Teil von) #5453.
    Es ermöglicht auch:
  • Arbeiten mit booleschen Literalen auf Typebene, was bisher unmöglich war
  • Unwrapping-Typen für flatMap -ähnliche Operationen wie diesen promised -Vorschlag
  • Hinzufügen von Einschränkungen zur Typ-/Funktionseingabe, das Thema von #15347 und eine Idee, die ich aus dem Buch „Typgesteuerte Entwicklung mit Idris“ zu abhängigen Typen habe. Dies könnte helfen, 0 -Teiler nicht zuzulassen -- im Wesentlichen ein Hack zum Subtrahieren von Typen, dem Thema von #4183. Ich bin mir sicher, dass ich mir die meisten Anwendungsfälle dort noch nicht vorgestellt habe, aber das könnte damit erledigt werden, zB function div<B extends number, NotZero = { (v: '1') => 'whatever'; }({ (v: 0) => '0'; (v: number) => '1'; }(B))>(a: number, b: B) . Normalerweise wirbt die Idris-Sprache mit längensicheren Vektor-/Matrixoperationen, aber das braucht dies nur für Funktionen höherer Ordnung.
  • Eine Möglichkeit, Union<T> / NonUnion<T> Einschränkungen für Eingabetypen auszudrücken, indem die obigen Einschränkungen + IsUnion verwendet werden. -- dieser Typ ist irgendwie kaputt gegangen und ich bin mir nicht mehr sicher, wie ich das anstellen soll.
  • auch #5453 gegeben, hilft dies auch beim Eingeben von Funktionen wie curry , Function.prototype.bind , Promise.all (keine vollständige Liste, nur einige Funktionen, die andere Leute als herausfordernd bezeichnet haben).
  • switch -Logik vorgeschlagen in #13500, hier gelöst durch Funktionsüberladungs-Musterabgleich
  • diese zugeordneten bedingten Typen, die das Thema von #12424 und #17077 ( promised ) sind, auch bekannt als Typprüfungen auf Typebene. Das würde es jedoch nicht lösen - selbst wenn das heute überall zur Erzeugung von booleschen Literalen verwendet werden könnte, hatten wir bis zu diesem #6606-Land noch keine Möglichkeit, auf der Typebene auf solchen booleschen Literalen basierende Bedingungen zu bearbeiten / auszuführen ohnehin.
  • Überprüfen, ob String-Indizes für Objekttypen vorhanden sind, um dies über Operationen hinweg beizubehalten (wie Omit , Overwrite aus #12215), siehe meinen Kommentar in #12424
  • Basierend auf den obigen String-Indexprüfungen gibt es eine Möglichkeit, den Objekttyp-Eigenschaftszugriff so zu reparieren, dass Prüfungen auf Strings, die mit Namen von Objekt-Prototypmethoden übereinstimmen (z. B. toString , toLocaleString ), eher in den String-Index aufgelöst werden als bei den Prototyp-Methoden, wodurch möglicherweise Störungen im Zusammenhang mit dem toString -Objektzugriff über alle anderen Typoperationen behoben werden, die zuvor vom fehlerhaften integrierten Objekteigenschaftszugriff abhängig waren.

Bearbeiten: Durchgestrichene Anwendungsfälle, die seit dem Zeitpunkt des Schreibens von bedingten Typen (#21496) gefüllt wurden.

@Igorbeks Vorschlag eines Expression-First-Ansatzes würde (auf Kosten einiger unbekannter Anwendungen eines Teils der obigen Liste vielleicht brechen - mir ist noch nichts Konkretes eingefallen, obwohl ich Angst habe, in eine Ecke gemalt zu werden ) versprechen zusätzlich, die Lücke zwischen Wert- und Typebene zu schließen, die derzeit Dinge wie diese Funktionsanwendung (dies #6606), Spread/Rest (#5453), Assertion-Operator ! (#17370), Union-Typ umfasst Subtraktion durch Type Guards (#4183, ansonsten erreichbar durch die oben genannten Einschränkungen) und möglicherweise mehr, was mir nicht auf Anhieb einfällt.

Ich denke, dieser ganze Kompromiss könnte einige Fragen aufwerfen, wie zum Beispiel, warum die gleiche Funktionalität auf zwei Arten zugänglich gemacht wird (tatsächliche Typebene, Ausdrucksebene, auf die eingebettet in der Typebene zugegriffen wird).

Bearbeiten: Meinen Vergleichskommentar oben mit einer zusätzlichen potenziellen Herausforderung aktualisiert.

Bearbeiten 2:

Es ist bedauerlich, dass der ursprüngliche Post Sie glauben machen wollte, dass dies nur das Schreiben einiger Grenzfälle ohne eine null & Problemumgehung ermöglichen würde, während dies in Wirklichkeit das kritische Merkmal ist, das TS derzeit ohne bekannte Problemumgehungen für Umgebungskontexte zurückhält .

Das bedeutet, dass es TS betrifft, die in separaten .d.ts -Dateien geschrieben sind, was die Norm nicht nur für stdlib.d.ts ist, sondern auch für alle DT-Typisierungen, die separat von ihren ursprünglichen JS-Projekten geschrieben wurden, was sich selten ändern wird während JS-Bibliotheken auch für Alternativen wie Flow offen bleiben wollen.

(Dies ist für .ts -Eingaben nicht viel besser, da Typen, die die „Umgehung“ des OP verwenden, nicht so parametrisiert werden können, dass sie in wiederverwendbare Typen auf höherer Ebene zusammengesetzt werden, ohne die Ausdrucksebene zu beeinflussen.)

@tycho01 Danke für die Liste; Da sind viele Links drin, die ich verdauen muss. Ich möchte unbedingt auch den Musterabgleich für Typen, und #6606 ist eine gute, pragmatische Lösung für das Problem. Es scheint mir jedoch, dass es eine zunehmende Verwirrung zwischen den Wert- und Typebenen gibt, und #6606 wird die Dinge in diesem Bereich nicht verbessern.

Der Grund, warum diese Funktion benötigt wird, liegt darin, dass wir keine Möglichkeit haben, Typausdrücke zu erstellen, die entsprechen:

  • der Rückgabetyp eines Funktionstyps, wenn er mit Argumenten bestimmter Typen angewendet wird
  • (vor typeof K[L] ) der Typ einer Eigenschaft für einen Objekttyp, der einem Schlüssel eines Zeichenfolgenliteraltyps entspricht
  • (evtl. andere, ich muss deine Liste mal genauer durchgehen)

Ich bin der Meinung, dass es tatsächlich möglich sein sollte , Ausdrücke auf reiner Typebene für diese zu konstruieren, ohne auf die Vermischung von Konstrukten auf Wertebene und Ausdrücken auf Typebene zurückzugreifen. Es wäre schön, wenn Typen wie (a: A) => B oder A | B Zucker für einfache parametrisierte Typen wie Func<A, B> , Union<A, B> wären und wir allgemeine Werkzeuge zum Manipulieren parametrisierter Typen hätten (HKTs, Fundeps oder Typfamilien etc.).

Ich weiß, dass ein übermäßiger Fokus auf Solidität nicht eines der Ziele von TypeScript ist, aber es gibt jetzt viele Konzepte auf Typebene, die auf undurchsichtige Weise miteinander interagieren. Eine Art Formalisierung dessen, was ein Typ ist, und eine Möglichkeit, mechanische Regeln dafür vorzuschreiben, wie ein Typ mit anderen Typen interagiert (Subtypisierung, Destrukturierung usw.), würde viel bewirken.

Ich bin der Meinung, dass es tatsächlich möglich sein sollte , Ausdrücke auf reiner Typebene für diese zu konstruieren, ohne auf die Vermischung von Konstrukten auf Wertebene und Ausdrücken auf Typebene zurückzugreifen.

Oh, ja, ich persönlich hatte überhaupt nicht vor, auf Wertekonstrukte zurückzugreifen – das ist alles, was ich versuche. Wenn die Werteebene unverzichtbar wäre, wäre ich hier vielleicht selbst im ausdrucksbasierten Lager gewesen. :P

Ich schätze, die Kluft zwischen Wert- und Typenebene wird sich meistens verkleinern (bewegt sich TC39 schneller als TS?), und afaik, die Lücken haben bereits hervorragende Vorschläge auf Typenebene (siehe unten in meinem vorherigen Beitrag).

Verdammt, mir ist klar, dass das Erstellen von Eingaben für einige der Funktionen außerhalb der Erfahrung der meisten Benutzer liegen wird.
So wie ich das sehe, möchte ich, dass die Standardbibliothek und die FP-Bibliotheken so gut typisiert werden, dass TS-Benutzer diese einfach schreiben können und die Inferenz automatisch für sie erledigt wird.
TS hat nur wenige Operatoren auf Typebene, aber das Lösen tatsächlicher Probleme mit ihnen (das beste Beispiel ist Overwrite / Omit #12215) mag für gelegentliche Webentwickler wie eine Raketenwissenschaft erscheinen. Verdammt, es hat auch bei uns bis vor kurzem gedauert, und sie sind noch nicht einmal prototyp- / index- / symbolsicher.

Es wäre schön, wenn Typen wie (a: A) => B oder A | B waren Zucker auf einfachen parametrisierten Typen wie Func , Union

Wir können es umdrehen und die parametrisierten Typen als Aliase / Typkonstruktoren erstellen. Für eine Typoperation, die ein Foo<Bar> nimmt, spielt es keine Rolle, ob sich Ihr Ding zu eins vereinfacht – es wird nur geprüft, ob es zur Beschreibung passt.
Das ist ziemlich genau das, was stdlib.d.ts tut – Sie haben ein foo[] , aber es erfüllt die Array<Foo> Beschreibung und funktioniert daher mit seinen Array.prototype Eingaben.
Nicht so wirklich hilft aber:

type Union2<A, B> = A | B;
type TuplizeA<Tpl extends Union2<any, any>, A, B> = [Tpl[0], Tpl[1]];
// ^ sorry, no way to access union members through e.g. [0]
// type a = TuplizeA<1 | 2>;
type TuplizeB<Tpl extends any | any> = [Tpl[0], Tpl[1]];
// ^ sorry, no way to access union members through e.g. [0]
// type b = TuplizeB<1 | 2>;
type TuplizeC<Tpl extends Union2<A, B>, A, B> = [A, B];
type c = TuplizeC<1 | 2>;
// ^ need 3 arguments, maybe fixable with #14400
type TuplizeD<Tpl extends A | B, A, B> = [A, B];
// ^ need 3 arguments, maybe fixable with #14400
type d = TuplizeD<1 | 2>;

Also ähm ja, ich habe es nicht gelöst, aber gerade festgestellt, dass kubes # 14400 dort tatsächlich helfen könnte. Und ich habe dich heute auch erst dort gesehen!
Das Gleiche gilt eigentlich für Funktionen - was eine gute Erinnerung daran ist, dass #14400 dort möglicherweise nicht nur Funktionsrückgabetypen, sondern auch Parametertypen ausführt.

Dieser Ansatz müsste im Moment Überladungen verwenden, was bedauerlich ist, da er nicht skaliert, aber ja. Um es wieder generisch zu machen, würden wir im Wesentlichen diese #6606-Mustervergleichsüberladungen verwenden, um die richtige Option für verschiedene Arten auszulösen.
Ich nehme an, man könnte das verwenden, um sie generisch in Tupeltypen umzuwandeln und sie dann mit meinem inkrementellen Ansatz zu durchlaufen, um sie irgendwie zu bearbeiten.
Für Gewerkschaften hatte ich auf eine schönere Möglichkeit gehofft, sie in Tupeltypen umzuwandeln. Es würde jedoch eine willkürliche Reihenfolge hinzufügen und kann sich auch keine hübsche Syntax / Schlüsselwort ausdenken.

Bearbeiten: Um diese Funktionslücken auf Ausdrucks- / Typebene erneut zu behandeln:

  • Funktionsanwendung: diese #6606
  • Behauptungsoperator ! (#17370): nach diesem #6606 ist dies gelöst
  • Union Type Subtraction through Type Guards (#4183): nur ein allgemeiner Fall von ! oben, wobei dieser #6606 auch durch Constraints erreichbar ist (zB NotZero oben).
  • Spread/Rest (#5453) -- selbst wenn Tupel bis dahin nicht manipuliert werden können, können die ähnlichen ArrayLike s, siehe die List Operationen in meinem Gist . Mit diesem #6606 denke ich jetzt, dass wir Parameter aus nicht angewendeten Funktionen extrahieren könnten , obwohl das Extrahieren im Moment der Anwendung (dh um ihre genauen Eingabewerte zu erhalten) immer noch 5453 benötigen würde.

Kurz gesagt, wenn dieser Vorschlag landen würde, würde ich sagen, dass eine Funktionslücke zwischen Ausdrucks- und Typebenen kein besonders großes Argument für den Vorschlag mit Ausdrucksgeschmack hier wäre. Wenn 5453 auch drin wäre, würde mir da keiner mehr einfallen. Und beachten Sie, dass für seltene Ausnahmen die im ursprünglichen Beitrag hier angegebene Problemumgehung weiterhin gültig wäre.

Nun, ein Argument, das immer noch leicht dafür vorgebracht werden kann, wäre, dass mit der ausdrucksorientierten Variante, noch bevor die Typebene in Spiegelungsoperatoren einholt, diese Funktionalität unter der gleichen Syntax wie in JS (ohne Problemumgehungen) bereitgestellt würde. Lernkurve reduzieren.

Bearbeiten 2:

Ich habe gerade festgestellt, dass der Trick des Ausdrucksvorschlags, Typen auf die Ausdrucksebene zu bringen, 1 as any as MyType , logischerweise auch für die Funktion selbst funktionieren sollte.

Dies bedeutet wahrscheinlich, dass die tatsächliche Funktionalität, die von beiden Varianten ermöglicht wird, etwas ähnlich erscheint, äußere Unterschiede bestehen hauptsächlich aus typeof myVar (Typ-Variante) vs. myVar (Ausdrucksvariante), um Variablen in der Funktionsanwendung zu verwenden; um Typen darin zu verwenden MyType (Type Flavor) vs. 1 as any as MyType (Expression Flavor, alternativ declare let a: any; dann <MyType>a ).

AST-Änderungen von beiden scheinen ebenfalls ziemlich überschaubar zu sein. Die Ausdrucksvariante benötigt nur die typeof -Konjunktion, um stattdessen auf einen Wertausdruck zu zeigen; Typ-Flavor würde die vorhandene Funktionsanwendungssyntax ( fn<T>(arg) ) von der Ausdrucks- auf die Typebene kopieren und sich in die vorhandene Implementierung einklinken, wie von Ryan oben vorgeschlagen.

Ich denke dann kommt es auf folgendes an:

Fall für Ausdrucksgeschmack:

  • typeof expr mit JS-Syntax ohne Problemumgehung vor Unterstützung auf TS-Typebene

Fall für Typgeschmack:

  • keine Breaking Changes über Priorität
  • Wertausdrücke können durch Problemumgehung noch erfasst werden (oder durch TS, wenn sie unter einer anderen Syntax stehen, bis die Operatoren aufholen)
  • MyType statt 1 as any as MyType : keine Typebene in Ihrer Ausdrucksebene in Ihrer Typebene in Ihrer Ausdrucksebene.

Ein verwandtes Thema, das hier bisher unberührt blieb, war die Bereitstellung this -Bindungen in dieser Typ-Level-Funktion „Anwendung“. Jetzt delegiert JS das Überschreiben an die Function.prototype -Methoden, aber so wie es aussieht, fehlt diesen eine Möglichkeit, dies auf Typebene zu handhaben.

Zufällige Beispielsyntax bei gegebenem Funktionstyp F (this: Foo, a: number, b: string) => SomeReturnType , der andernfalls möglicherweise als F(MyA, MyB) aufgerufen worden wäre: F(this: MyFoo, MyA, MyB) .

Das Berechnen des Rückgabetyps ohne Überschreiben der Bindung this wäre immer noch wie F(MyA, MyB) und spiegelt wider, wie das this -Argument auf Typebene normalerweise ignoriert wird, wenn Sie versuchen, eine solche Funktion zu verwenden die Ausdrucksebene.

Vorteile dieser Beispielsyntax:

  • spiegelt die Deklarationssyntax wider

Nachteile dieser Beispielsyntax:

  • spiegelt die Deklarationssyntax wider

Es stellt sich also heraus, dass dies bereits in der Sprache ist!

Seien Sie nicht zu aufgeregt.

@DanielRosenwasser hat mich gerade auf #12146 hingewiesen, einen Fehler, der es ermöglicht, Funktionsaufrufe für Literale anstelle von Typen zu verwenden.

_5 Minuten später_

TA Dah! Ein schrecklich böses Ding, das wir niemals in der Produktion verwenden sollten. Aber es ist verlockend...

interface String {
    passthrough<T>(v: T): T;
}

// All work
type ANumber = "".passthrough(10 * 10);
type AString = "".passthrough("hello" + "world");
type AHelloWorld = "".passthrough("hello world");
type AnArbitraryThing = "".passthrough(Object.assign({hello: "world"}, {foo: "bar"}));
type ThisCraziness = "".passthrough((() => "cows are big dogs"));

~Das lässt die Effort: Difficult zu diesem Thema etwas zweifelhaft aussehen, sieht aus, als hätten sie es dort drüben versehentlich gemacht.~ Ich lese mehr und fühle mich albern, das _ist_ schwierig, gut zu machen.

Viel Spaß @tycho01.

@TheOtherSamP Ich habe dies mit TypeScript 2.4.2 versucht und alle diese Typen werden als any abgeleitet.

@pelotom Huh, es funktioniert hier auf 2.4.2 und 2.5.0-dev.20170803. Zielen Sie auf es6 und den strikten Modus ab.

image

Sieht aus, als hätten sie es gerade repariert, aber ich fürchte, das könnte meine Schuld sein. #17628

@TheOtherSamP Nein, keine Würfel. Nun ja.

@pelotom Das ist seltsam, es funktioniert in einem völlig neuen Projekt für mich, ich weiß nicht, was an unseren Setups anders wäre.

@TheOtherSamP : haha, das ist ziemlich lustig.
Zeitlich sieht es so aus, als hätten sie dort etwas vor Ihrem Kommentar mit der Fehlerbehebung begonnen. Nun ja.

@pelotom :

Ich habe dies mit TypeScript 2.4.2 versucht und alle diese Typen werden als beliebig gefolgert.

Sein Ausschnitt scheint in Playground (2.3.2) zu funktionieren. Bei einer neueren Version außerhalb davon ( ^2.5.0-dev.20170626 ) habe ich auch Probleme beim Reproduzieren.

Das lässt die Effort: Difficult zu diesem Thema etwas zweifelhaft aussehen, sieht aus, als hätten sie es dort drüben versehentlich gemacht.

Sie bezog sich auf eine typbasierte Implementierung, was einige Änderungen bedeuten würde, während diese Ausdruckssprache zu verwenden scheint (-> + , Object.assign , weitere Funktionsaufrufe).

@tycho01 Ich _denke_, dass alles damit begonnen hat, dass ich in #17618 darauf aufmerksam gemacht habe. Na ja, das wird mich lehren, halb ernsthaft darüber nachzudenken, es in der Produktion zu verwenden.

Sie bezog sich auf eine typbasierte Implementierung, was einige Änderungen bedeuten würde, während diese Ausdruckssprache zu verwenden scheint (-> +, Object.assign, weitere Funktionsaufrufe).

Ja, ich war ein Idiot und habe diese ganze Ausgabe nicht durchgelesen, bis ich das gesagt hatte. Es ist eine Schande, dass dies wahrscheinlich eine bessere Version des Features ist, aber ich wünschte, wir könnten es jetzt haben. Ich gehe sehr an die Grenzen des Typsystems, und entweder dies oder #12424 würde so viele Optionen eröffnen.

Öffnete eine PR unter #17961.

@yortus Deckt dies den Fall von 'typeof literal' ab?
TypeScript erlaubt heute nicht, "const x: typeof 1 = 1;" zu schreiben.

@NN--- der ursprüngliche Vorschlag deckt alle Ausdrücke ab, aber so wie ich es verstehe, deckt der "genehmigte" Teil nur den Eigenschaftszugriff und die Rückgabetypen von Funktionen ab.

Selbst wenn typeof 1 erlaubt wäre, bin ich mir nicht sicher, ob es den wörtlichen Typ ( 1 ) oder den breiteren Typ ( number ) geben würde.

TypeScript erlaubt heute nicht, "const x: typeof 1 = 1;" zu schreiben.

Warum nicht const x: 1 = 1; ?

@SaschaNaz Ich wollte sowas schreiben

const a = {q:1};
const b = {q:1};
const x: ReadonlyArray<typeof a> = [a,b];

Aber ähnlich funktioniert nicht mit Literalen:

const x: ReadonlyArray<typeof 1> = [1,2,3];

@yortus Guter Punkt zum genauen Typ. An wörtliche Typen habe ich nicht gedacht.

@NN---: Ich glaube, Ihr Beispiel funktioniert bereits.

@tycho01 Flow hat jetzt den Typ $Call , um den Rückgabetyp der Funktion https://github.com/facebook/flow/commit/ac7d9ac68acc555973d495f0a3f1f97758eeedb4 zu erhalten

Wäre das Zulassen von nur typeof fn(...) nicht dasselbe wie das Zulassen typeof eines beliebigen Ausdrucks?

function fn() {
  return /** whatever expression you like */;
}
type a = typeof fn();

außer dass Sie jetzt eine Laufzeitfunktion zu keinem anderen Zweck erstellen, als einen Typ herauszufinden?

Nicht wirklich. Sie führen eine Typauswertung eines Ausdrucks durch, nicht eine Ausführung des Ausdrucks.

@dyst5422 typeof fn() würde den Ausdruck nicht wirklich auswerten, es würde Ihnen nur den Rückgabetyp geben.

EDIT: Vielleicht wolltest du das sagen, bin mir nicht sicher. Aber ich denke, @sky87 sprach über das _Definieren_ einer Funktion für keinen Zweck, außer sie in einem Typausdruck zu verwenden, nicht _auszuwerten_.

@ dyst5422 , wie @pelotom sagte, ich meinte nicht, dass Sie die Funktion ausführen würden. Um näher darauf einzugehen: Wenn Sie das typeof von beliebigen Ausdrücken nicht zulassen, aber Sie das typeof des Rückgabetyps einer Funktion zulassen, was ich tun werde, um den komplizierteren Typ herauszufinden Ausdrücke besteht darin, sie in eine Funktion zu packen, damit ich nach ihrem Rückgabetyp fragen kann. Das erstellt eine Funktion zur Laufzeit, nur um ihren Rückgabetyp zu erhalten, und es ist mehr Boilerplate zum Schreiben.

BEARBEITEN: Sie können die Art der beliebigen Ausdrücke bereits herausfinden, das ist hässlich, funktioniert aber

const dummy = (false as true) && /* arbitrary exp */;
type TypeOfExp = typeof dummy;

Ich weiß ehrlich gesagt nicht, welchen Hack ich bevorzugen würde. Ich denke, das Beste wäre, wenn man mit typeof direkt nach dem Typ fragen könnte.

Ah, ich folge jetzt. Ja, ich denke, der bevorzugte Weg wäre, es als verwenden zu können

type TypeOfExp = typeof (
  false &
  "false" &
  0
)

Ausdruckstypauswertung willkürlich durchführen zu können

Wird es möglich sein, den Rückgabetyp eines new -Aufrufs abzufragen? Mein Anwendungsfall: Ich möchte Typanmerkungen für eine Funktion schreiben, die einen Verweis auf eine beliebige PromiseConstructorLike -Implementierung (z. B. $q oder Bluebird) akzeptiert und ein von dieser Implementierung erstelltes Promise zurückgibt.

declare function wait<P extends PromiseConstructorLike>(time: number, implementation: P): typeof new implementation<void>((res: any, rej: any) => void);

const bluebirdPromise = wait(1e3, Bluebird);
// typeof bluebirdPromise should be instance of Bluebird

Wird es möglich sein, Rückgabetypen ohne typeof abzufragen, oder müssen wir null as FnType abfragen?

interface Fn {
    (a: string): string;
    (a: number): boolean;
}
type Ret = Fn(number); // Ret = boolean
type Ret = typeof (null as Fn)(number);

Entschuldigung, falls diese Fragen bereits beantwortet wurden; Ich konnte sie nicht finden.

Was ist der Sinn von new hier, würden Sie nicht einfach typeof implementation() wollen?

Nein, da implementation() kein gültiger Aufruf ist. PromiseConstructorLike kann gemäß seinen Typdeklarationen nur über new aufgerufen werden. typeof implementation() ist ein Typfehler, genauso wie (typeof implementation)['foobar'] ein Typfehler wäre.

Spielplatz-Link

Ist es möglich, abgeleitete generische Typen einzuführen, wie es FlowType getan hat? Zumindest kann es das Problem lösen, den Typ des Rückgabewerts von Funktionen zu erhalten.

type _ExtractReturn<B, F: (...args: any[]) => B> = B;
type ExtractReturn<F> = _ExtractReturn<*, F>;

@Cryrivers : siehe #14400 für diesen Ansatz. Es löst jedoch nicht das Problem, bei dem der Ausgabetyp von der Eingabe abhängt.

Am Ende brauchte ich dies heute noch einmal, um anzudeuten, was ein Aufruf einer Funktion dynamisch zurückgeben würde, sicher, ich wünschte, es würde Priorität bekommen.

Ein ReturnType<T> -Operator wird in TS 2.8 zu lib.d.ts hinzugefügt , unterstützt durch bedingte Typen.

Da ReturnType<T> noch Rückgabetypen berücksichtigen muss, die von Argumenttypen abhängig sind, finden Sie hier als Referenz die Implementierung des Typs $Call Flow.

Bearbeiten: Entschuldigung @goodmind , ich hatte nicht bemerkt, dass Sie bereits genau darauf verlinkt haben.

Ich habe meinen früheren Beitrag zu Anwendungsfällen dieses Vorschlags (oder seiner Typaufrufinterpretation) basierend auf den jüngsten TS-Ergänzungen aktualisiert.
Anwendungsfälle für den Musterabgleich werden jetzt von #21496 abgedeckt, wobei ... die Fälle verbleiben, in denen wir Typen basierend auf vom Benutzer bereitgestellten Lambdas berechnen möchten, z. B. curry , Funktionszusammensetzung, map , reduce , Linsenbearbeitung basierend auf einem Lambda ... das lustige Zeug. :)

PS @thorn0 : Ich denke, Ihr Angular-Anwendungsfall kann jetzt mit ReturnType (#21496) gefüllt werden!

Ich denke, dies sollte abgedeckt werden, indem diese Ausdrücke zugelassen werden.
prop2: typeof this.prop1.big.complex;

@mhegazy Gibt es ein separates Problem, um dies zu verfolgen?
Es ist ärgerlich, dass typeof für lokale, aber nicht für Eigenschaften funktioniert, während es für statische Eigenschaften arbeitet.

class A {
    x: number;
    static y: number;
    f() {
        const a: number = 1;
        const b: typeof a = 2; // OK
        const c: this.x = 3; // No :(
        const d: this['x'] = 3; // OK
        const e: typeof A.y = 4 // OK
    }
}

@NN--- Sie können dafür immer indizierte Typen verwenden:

this['x']

@cameron-martin Funktioniert nicht. Spielplatz

@tycho01 die neuen Typ-Inferenzen und -Bedingungen sind großartig, aber auch super ärgerlich, dass sie nicht für das Überladen von Funktionen funktionieren. Als Grund wurde angegeben, dass so etwas typeof benötigt wird, um den Funktionstyp aus den möglichen aufzulösen.

@NN verwenden Sie einfach const d: this['x'] = 3;

Oh schön :)

@NN--- oder verwenden

class A {
    x: number;
    static y: number;
    f() {
        const self = this;
        const a: number = 1;
        const b: typeof a = 2; // OK
        const c: typeof self.x = 3; // OK
        const d: typeof self['x'] = 3; // OK
        const e: typeof A.y = 4 // OK
    }
}

@tsofist Mir ist bewusst, dass lokal funktioniert, aber ich finde das hässlich.
Dies entspricht dem manuellen Speichern von „this“ für den Callback „function(){}“, anstatt Lambda mit impliziter Erfassung zu verwenden.

@NN

das hässlich.

Ja, das ist nur eine Option :)

Bedingte Typen machen dies jetzt weitgehend irrelevant, da Sie ReturnTypeOf<T> zusammen mit bestimmten anderen Aliasen schreiben können, wenn Sie einen bestimmten Satz von Argumenten validieren möchten. Sie können keine Überlastungsauflösung durchführen, aber wir glauben nicht, dass diese Funktion die Komplexität nur für diesen Anwendungsfall wert ist.

@RyanCavanaugh Ich glaube du meinst ReturnType<T> ?

@RyanCavanaugh das ist bedauerlich - die Überlastungsauflösung ist das, was ich wirklich brauchte. Gibt es ein weiteres Problem, um das Hinzufügen einer Überladungsauflösung zu bedingten / Infer-Typen zu verfolgen?

Du solltest es schreiben können:

type Return1<A1, T extends (a: A1) => any> = T extends (a: A1) => infer R ? R : any;
type Return2<A1, A2, T extends (a: A1, a: A2) => any> = T extends (a: A1, a: A2) => infer R ? R : any;

declare function something(a: number): number;
declare function something(a: string): string;
declare function something(a: number, b: string): boolean;

type A = Return1<number, something>; // number
type B = Return1<string, something>; // string
type C = Return2<number, string, something>; // boolean

Ich habe es jedoch nicht getestet, und Sie würden für jede Anzahl von Argumenten einen separaten Helfer benötigen.

@ForbesLindesay : Das something ist derzeit eine Variable auf Ausdrucksebene - das Verweisen darauf mit z. B. typeof hier (oder das Deklarieren als Schnittstelle) behebt das. Ich schaffe es jedoch nicht, geeignete Rückgabetypen zu liefern (auf 2.8.0-dev.20180318 ).

@ForbesLindesay leider glaube ich nicht, dass das funktioniert; Der Infer-Mechanismus wählt die _last_-Methodenüberladung:

type Funcs = ((p1: string, p2: string) => void) & ((p1: number) => void);

type FuncPromise1<T> = T extends (p1: infer P1) => void ? (p1: P1) => Promise<[P1]> : never;
type FuncPromise2<T> = T extends (p1: infer P1, p2: infer P2) => void ? (p1: P1, p2: P2) => Promise<[P1, P2]> : never;

let foo: FuncPromise1<Funcs> & FuncPromise2<Funcs>;

image

Der Infer-Mechanismus _ist_ jedoch in der Lage, mit Tupel-Vereinigungen umzugehen:

type Tuples = [string, string] | [number];

type TuplePromise1<T> = T extends [infer P1] ?  (p1: P1) => Promise<[P1]> : never;
type TuplePromise2<T> = T extends [infer P1, infer P2] ? (p1: P1, p2: P2) => Promise<[P1, P2]> : never;

let foo: TuplePromise1<Tuples> & TuplePromise2<Tuples>;

image

Vielleicht brauchen wir etwas, um Überladung -> Objekt und Funktion -> Objekt zu ermöglichen, Entpacken. Führen Sie dort die Typzuordnung und die Inferenz durch und wickeln Sie sie dann wieder in eine Funktion und Überladung um.

@MeirionHughes :

Vielleicht brauchen wir etwas, um Überladung -> Objekt und Funktion -> Objekt zu ermöglichen, Entpacken. Führen Sie dort die Typzuordnung und die Inferenz durch und wickeln Sie sie dann wieder in eine Funktion und Überladung um.

Wie (a: number, b?: string) => boolean -> { a: number, b?: string } ? Wir können noch keine Parameternamen auf diese Weise erhalten, aber konzeptionell wird dies für Restparameter ( (a: number, ...b: string[]) => boolean ) schwieriger, auch da wir keine Objekttypen verwenden können, um eine Reihenfolge zu erstellen.

Die Reihenfolge ist wahrscheinlich wichtiger als Namen, und wir können zwischen Parametern und Tupeln konvertieren. Spreads / Optionals können es auch noch etwas erschweren.

Dies reduziert das Problem auf das Extrahieren der Überladungen. Überladungen sollten eine Schnittmenge von Funktionstypen wie ((a: number) => 123) & ((s: string) => 'hi') sein, also besteht das Problem darin, wie man einen Schnittpunkttyp (z. B. in einen Tupeltyp) 'entpackt' -- bis jetzt haben wir das nicht.

Ich finde diesen Pfad unbefriedigend, da er den Überlastungs-Anwendungsfall ansprechen würde, aber nicht Generika sagen würde, aber ja. Das Auspacken der Kreuzung war in beiden Richtungen immer noch eine Lücke.

Da dieses Thema nun abgeschlossen ist, gibt es schon einen neuen Vorschlag für die noch fehlenden Teile? Möchten Sie mit dem Rückgabetyp abhängig von Argumenten umgehen?

Da dieses Thema nun abgeschlossen ist, gibt es schon einen neuen Vorschlag für die noch fehlenden Teile?

keine, die mir bekannt sind.

Möchten Sie mit dem Rückgabetyp abhängig von Argumenten umgehen?

glaube nicht, dass wir ein Problem mit der Verfolgung von Anruftypen haben.

Gibt es vorläufige Unterstützung für die Idee, nur eine Funktionsanwendung auf Typebene hinzuzufügen? Dazu könnte ich einen Vorschlag schreiben. Syntaktisch denke ich, dass das der einfachste Weg ist.

type MyType<A> = {
    foo: A
}

type Wrap = {
    <T>(maybe: MyType<T>): MyType<T>;
    (maybe: any): MyType<any>;
}

type Naive = ReturnType<Wrap>; // Naive = { foo: any }
type Proposed1 = Wrap(maybe: number); // Proposed1 = { foo: number }
type Proposed2 = Wrap(maybe: MyType<number>); // Proposed2 = { foo: number }
type Proposed3 = (<T>(maybe: T) => MyType<T>)(maybe: number) // Proposed3 = { foo: number }

Grenzfälle:

const foo = <T>(a: T) => T:

type Edge1 = (typeof foo)(a: number) // Probably trivial?

type Foo = {
    <T>(a: string): T
}

type Edge2 = Foo<number>(a: string) // Should this be allowed? Probably not, because:

type Bar<A> = {
    (a: string): A
}

type Edge3 = Bar<number>(a: string) // Things are getting strange

interface Baz<A> {
    <T>(a: T): T | A
}

type Edge4 = Baz<number>(a: string) // What is this?

Gibt es vorläufige Unterstützung für die Idee, nur eine Funktionsanwendung auf Typebene hinzuzufügen? Dazu könnte ich einen Vorschlag schreiben. Syntaktisch denke ich, dass das der einfachste Weg ist.

Zur Zeit nicht. Wir wollen die Überladungsauflösung wirklich nicht in den Raum höherer Ordnung stellen; Der Prozess ist ziemlich kompliziert und umfasst mehrere Durchgänge zum Ableiten von Typparametern und kontextabhängigen Typargumenten usw.. Dies in höherer Reihenfolge zu tun, ist erstens eine Menge Arbeit und zweitens wird es Leistungsherausforderungen geben, auf die wir nicht vorbereitet sind vorerst.

@mhegazy hat sich die Haltung des Teams dazu angesichts der jüngsten Arbeit in #24897 überhaupt geändert?

Es scheint einige Probleme zu geben, deren Lösungen auf einen $Call -Typ reduziert werden könnten, und ein $Call -Typ würde die Tür zu einer relativ einfachen Möglichkeit öffnen, höherwertige Typen zu emulieren; siehe zum Beispiel https://gist.github.com/hallettj/0fde5bd4c3ce5a5f6d50db6236aaa39e (ersetzen Sie die Verwendung von $PropertyType und $ObjMap durch $Call ). BEARBEITEN: Zusätzliches Beispiel: https://github.com/facebook/flow/issues/30#issuecomment -346674472

Eine solche Funktion würde wohl der Erfolgsbilanz von TypeScript entsprechen, eine vernünftige gemeinsame Lösung für viele Probleme zu finden, oder?

Bedingte Typen machen dies jetzt weitgehend irrelevant, da Sie ReturnTypeOf<T> zusammen mit bestimmten anderen Aliasen schreiben können, wenn Sie einen bestimmten Satz von Argumenten validieren möchten. Sie können keine Überlastungsauflösung durchführen, aber wir glauben nicht, dass diese Funktion die Komplexität nur für diesen Anwendungsfall wert ist.

@RyanCavanaugh @mhegazy

Ich stimme zu, dass es möglich ist, Dinge mit bedingten Typen zu tun. Ich denke, dass es nicht viel zusätzliche Komplexität in den Compiler bringen würde, wenn wir User.avatar in User extends { avatar: infer T } ? T : never umschreiben würden? So könnten wir zum Beispiel schreiben

export type Avatar = User extends { avatar: infer T } ? T : never;

als

export type Avatar = User.avatar;

um die Lesbarkeit zu verbessern.

Vollständiges Beispiel

Angenommen, wir laden und transformieren einige Daten und erhalten am Ende eine Funktion findUser wie diese

export function findUser() {
  return {
    username: 'johndoe',
    avatar: {
      lg: '1.jpg',
      s: '2.jpg'
    },
    repos: [
      {
        name: 'ts-demo',
        stats: {
          stars: 42,
          forks: 4
        },
        pull_requests: [
          { date: '2019-08-19', tags: ['bug', 'agreed-to-cla'] },
          { date: '2019-08-10', tags: ['bug', 'includes-tests'] },
          { date: '2019-08-07', tags: ['feature'] }
        ]
      }
    ]
  };
}

Dank der Inferenz aus abgebildeten Typen können wir den Typ wie folgt aus der Funktion extrahieren:

export type User = ReturnType<typeof findUser>;
export type Avatar = User extends { avatar: infer T } ? T : never;

Vorschlag: Dies sollte dasselbe auswerten

export type Avatar = User.avatar;

Zusätzlich könnten wir sogar behaupten, dass User.avatar nicht vom Typ never $ sein darf.

Mehr Beispiele

export type Repositories = User extends { repos: infer T } ? T : never;
export type Repository = User extends { repos: (infer T)[] } ? T : never;
export type RepositoryStats = Repository extends { stats: infer T } ? T : never;
export type PullRequests = Repository extends { pull_requests: (infer T)[] } ? T : never;
export type PullRequest = Repository extends { pull_requests: (infer T)[] } ? T : never;
export type Tags = PullRequest extends { tags: infer T } ? T : never;
export type Tag = PullRequest extends { tags: (infer T)[] } ? T : never;
export type Repositories = User.repos;
export type Repository = User.repos[];
export type RepositoryStats = User.repos[].stats;
export type PullRequests = User.repos[].pull_requests;
export type PullRequest = User.repos[].pull_requests[];
export type Tags = User.repos[].pull_requests[].tags;
export type Tag = User.repos[].pull_requests[].tags[];

Wenn Sie eine verschachtelte Eigenschaft auf einmal abbilden, ist nicht sehr klar, was passiert

export type Tag2 = User extends { repos: { pull_requests: { tags: (infer T)[] }[] }[] } ? T : never;

Das würde einiges klären

export type Tag = User.repos[].pull_requests[].tags[];

Eckfall

export class Hello {
  static world = 'world';
  world = 42;
}
export type ThisWillBeANumber = Hello extends { world: infer T } ? T : never;
export type ThisWillBeANumber = Hello.world;
export type ThisWillBeAString = (typeof Hello) extends { world: infer T } ? T : never;
export type ThisWillBeAString = (typeof Hello).world;

@lukaselmer Es scheint, als wolltest du nur

export type Avatar = User["avatar"];

was heute funktioniert

@lukaselmer Es scheint, als wolltest du nur

export type Avatar = User["avatar"];

was heute funktioniert

Genau danach habe ich gesucht. Ich habe in der Dokumentation danach gesucht, bin aber nicht fündig geworden. Danke!

Ist das Teil des Handbuchs oder gibt es eine offizielle Dokumentation darüber, wie das funktioniert? Ich bin selbst ziemlich vertraut damit, wie man es benutzt, aber wenn ich versuche, Leute auf die Dokumentation zu verweisen, kann ich nur eine Art von Wachen finden, was wirklich völlig anders ist

Mir ist also aufgefallen, dass dieser Vorschlag seit 2015 im Umlauf war und eines der ursprünglichen Ziele darin bestand, irgendwie den Typ einer einzelnen Eigenschaft einer Schnittstelle zu erhalten.

interface a {
 foo: bar;
 /* more types */
}

const example = (fooNeeded: [magic] a.foo ) => {};

Gehe ich richtig davon aus, dass dies nach 5 Jahren immer noch nicht möglich ist?

@MFry Ich denke, du suchst nach dieser Syntax: a['foo']

Wissen wir, ob es dafür schon eine Lösung gibt?

Ich versuche so etwas zu bekommen:

declare function something<A, B>(): void;

type Payload = string;

const hello = something<{}, Payload>();

declare function doThing<T extends ReturnType<typeof something>>(arg: T): { payload: unknown };

doThing(hello).payload === 123; // this should validate to a string aka type Payload

https://www.typescriptlang.org/play/index.html?ts=4.0.0-dev.20200512#code/CYUwxgNghgTiAEAzArgOzAFwJYHtXwGccBbEDACy1QHMAeAQQBp4AhAPgAoBKALngDccWYAG4AUGIwBPAA4IAClCkQcUYPAC8hDDCrVxYsHgIZ45EBBWbCJMpRq0A3gF9mi5auCcuB0JFgIKOjYePDAOAAq9nQR8CAAHhggqMAE8ABKZMgwqBGyILTScjiINqQUemycsNR8EbzwjvAySipqfGgA1qg4AO74zr6R0RzmljhcAHQtHmqaGloAjABMAMwi8AD0m -AVaQTkOMgQ6vxQEMJQSbs48FDaujR3nfdFCq2eYkA

Hallo @maraisr , ich bin mir nicht 100% sicher, was du erreichen willst. In Ihrem Beispiel nimmt something zwei Typen, verwendet sie aber nicht, und hello ist der Rückgabewert von etwas, das immer void sein wird; doThing sieht also zu keinem Zeitpunkt den Typ string .

Vielleicht ist so etwas wie unten, was Sie wollen?

declare function something<ReturnType>(): ReturnType;

type Payload = string;

const hello = () => something<Payload>();

declare function doThing<F extends () => any>(f: F): { payload: ReturnType<F> };

doThing(hello).payload === 'a string';

Ah ja - das tut mir leid. Danke für die schnelle Antwort!! :100: @acutmore

Das void sollte nur anzeigen, dass der Rückgabetyp dieser Funktion irrelevant ist. Diese beiden Typen werden an andere generische Typen weitergeleitet, die letztendlich in Argumenten verwendet werden.

etwas wie:

declare function something<A, B>(a: MyComplexGeneric<A>, b: B[]): { somethingA: number, somethingB: number };

// Those 2 generics influence the return object so they do get used as such. And the 2 arguments are roughly that. Its an object and an array, more-or-less. 

Meine Funktion doThing kümmert sich nicht wirklich darum, was die erste ( A ) generische Funktion ist, aber sie kümmert sich darum, was die zweite ( B ) ist.

Sehen Sie, dass something in meinem eigenen Anwendungsfall einen Nebeneffekt hat, der von doThing gelesen wird.

Ich kann also nicht einfach den ReturnType einer Funktion abrufen - ich muss irgendwie das Generikum einer erstellten Funktion aussaugen.


Wenn Sie der Meinung sind, dass diese Abfrage den Rahmen dieser Ausgabe sprengen würde, setzen Sie meine Reise auf StackOverflow fort!

@maraisr danke für die zusätzlichen Infos.

Wenn Sie möchten, dass doThing den ursprünglichen B -Typ von something erhalten kann, muss er irgendwie an hello übergeben werden. TypeScript betrachtet nur hello und weiß ohne Hilfe nicht, dass es sich um den Rückgabetyp von something handelt.

Dies ist eine Möglichkeit, dies zu tun:

/** Create a type that can store some extra type information **/
interface SomethingResult<T> {
    __$$__: T;
    somethingA: number;
    somethingB: number;
}

declare function something<A, B>(): SomethingResult<B>;

type Payload = string;

const hello = something<{}, Payload>();

declare function doThing<Result extends SomethingResult<any>>(arg: Result): { payload: Result['__$$__'] };

doThing(hello).payload === 1123; // error because `payload` is of type string
interface User {
  avatar: string;
}

interface UserData {
  someAvatar: User['avatar'];
}

@RyanCavanaugh Warum ist das geschlossen? Bedingte Typen lösen diesen und viele andere Anwendungsfälle nicht, und wenn dies zusammengeführt würde, würde es so viele Dinge möglich machen.

Ich arbeite an einer Funktion, die jeden Methodenaufruf in eine "punktfreie" Version umwandeln kann (Beispiel: [].map(() => n > 5) wird zu map(() => n > 5)([]) und das einzige, was fehlt, sind bedingte Typen und infer kann keine Generika erkennen, daher werden einige Typen in generischen Funktionen als unknown .

Wenn ich die Funktionen "aufrufen" könnte, um den Typ ( typeof myFunc(() => Either<string,number>) ) zu erhalten, wäre es möglich, diese Funktionalität zu haben (das ist derzeit unmöglich) und viele andere Dinge viel einfacher zu machen (HKTs usw.). .)

Ist die Komplexität sehr hoch, um $Call eine Funktion (wie im Flow) erstellen zu können? Ich habe das Gefühl, dass Typoskript es bereits automatisch macht.

@nythrox Wir glauben nicht, dass die syntaktische Verwirrung, zu der dies führen könnte, durch die Fälle aufgewogen wird, in denen Sie es benötigen, um zu einem bestimmten Typ zu gelangen. Der spezielle Fall des Auflösens eines Aufrufausdrucks wird an anderer Stelle verfolgt; Der Vorschlag im OP "jeden Ausdruck zulassen" ist unserer Meinung nach nicht gut für die Sprache geeignet.

@RyanCavanaugh oh okay, ich verstehe. Vielen Dank für die Antwort. Wissen Sie, welche Probleme beim Auflösen eines Funktionsaufrufs verfolgt werden?

Ich habe ein bisschen herumgesucht und kein Problem für einen Funktionsaufruf-Utility-Typ gefunden. Der einzige Verweis auf einen, den ich gefunden habe, war in #20352, der gerade auf diese Ausgabe zurückverwies.

Der spezielle Fall des Auflösens eines Anrufausdrucks wird an anderer Stelle verfolgt

@RyanCavanaugh Darf ich woanders verlinken? 🙂

Bei @tjjfvi #37181 geht es genauer darum, eine Funktion basierend auf ihren Eingaben aufzulösen. Könnte das sein, wonach Sie suchen.

@acutmore Das entspricht in etwa dem, wonach ich gesucht habe, obwohl ich speziell über ein flow-ähnliches $Call -Dienstprogramm oder eine andere Syntax gesprochen habe, um solche implementieren zu können. Der dort vorgeschlagene Ansatz ist seltsam, aber danke für den Link.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

weswigham picture weswigham  ·  3Kommentare

kyasbal-1994 picture kyasbal-1994  ·  3Kommentare

uber5001 picture uber5001  ·  3Kommentare

fwanicka picture fwanicka  ·  3Kommentare

siddjain picture siddjain  ·  3Kommentare