node.js - Wie man Intellisense für Parameterarten überladener Methoden ableitet oder bereitstellt
Ich arbeite in einer Electron-Anwendung. Es werden viele Ereignisse übertragen, mit spezifischen Listenern für jedes Ereignis. Zum Beispiel BrowserWindow.on:
Electron.BrowserWindow.on(event, listener)
event kann eines von 'always-on-top-changed', 'app-command', usw. usw... sein. Ich möchte eine Liste all dieser Ereignisse in einem einzelnen Typ haben, den ich zum Beispiel verwenden kann, um andere Funktionen daran zu hindern, ein ungültiges Ereignis zu übergeben.
Ich habe es versucht:
type BrowserWindowOnEvents = Parameters<InstanceType<typeof Electron.BrowserWindow>['on']>[0];Aber das liefert, zumindest für Intellisense, nur das zuletzt in der BrowserWindow-Klasse in electron.d.ts definierte Ereignis, das derzeit 'will-resize' ist. Ich erwarte eine Liste mit allen gültigen Ereignissen.
Wenn ich new BrowserWindow.on('') habe, liefert mir die Intellisense die Liste der Möglichkeiten, aber ich möchte Zugriff auf die Liste haben, wenn ich eine Typdefinition erstelle, nicht nur, wenn ich eine Instanz von BrowserWindow verfügbar habe.
---
Hier ist ein Link zum BrowserWindow von Electron
Und in electron.d.ts sind die Methoden so definiert:
class BrowserWindow extends NodeEventEmitter {
// Docs: https://electronjs.org/docs/api/browser-window
/**
* Emitted when the window is set or unset to show always on top of other windows.
*/
on(event: 'always-on-top-changed', listener: (event: Event,
isAlwaysOnTop: boolean) => void): this;
once(event: 'always-on-top-changed', listener: (event: Event,
isAlwaysOnTop: boolean) => void): this;
addListener(event: 'always-on-top-changed', listener: (event: Event,
isAlwaysOnTop: boolean) => void): this;
removeListener(event: 'always-on-top-changed', listener: (event: Event,
isAlwaysOnTop: boolean) => void): this;
...
}
Antwort #1
Es stellt sich heraus, dass es möglich ist, dies zu tun, aber es sieht nicht gut aus. Das Problem ist, dass die on-Methode mit einer separaten Überladung für jeden Ereignisnamen deklariert ist, und die Typinferenz bei überladenen Funktionen berücksichtigt nur die letzte (in diesem Fall die Überladung für 'will-resize').
Es gibt jedoch eine Möglichkeit, Typen aus überladenen Funktionen abzuleiten (siehe dieses TypeScript-Problem):
type Args<F extends (...args: any[]) => unknown> = F extends {
(...args: infer A1): void
(...args: infer A2): void
} ? [A1, A2] : never
declare function f(event: 'n1', l: (n: number) => void): void
declare function f(event: 'n2', l: (s: string) => void): void
type TestArgs = Args<typeof f>
// type TestArgs = [
// [event: "n1", l: (n: number) => void],
// [event: "n2", l: (s: string) => void]
// ]
Der Nachteil besteht darin, dass dafür ein Funktionsobjekt erstellt werden muss, das mindestens so viele Überladungen wie die Funktion aufweist, aus der wir die Parametertypen extrahieren möchten. Die on-Methode von BrowserWindow hat 36 Überladungen, also verwenden wir zur Sicherheit 40:
type BuildOverloads<
A01 extends unknown[], A02 extends unknown[], A03 extends unknown[],
A04 extends unknown[], A05 extends unknown[], A06 extends unknown[],
A07 extends unknown[], A08 extends unknown[], A09 extends unknown[],
A10 extends unknown[], A11 extends unknown[], A12 extends unknown[],
A13 extends unknown[], A14 extends unknown[], A15 extends unknown[],
A16 extends unknown[], A17 extends unknown[], A18 extends unknown[],
A19 extends unknown[], A20 extends unknown[], A21 extends unknown[],
A22 extends unknown[], A23 extends unknown[], A24 extends unknown[],
A25 extends unknown[], A26 extends unknown[], A27 extends unknown[],
A28 extends unknown[], A29 extends unknown[], A30 extends unknown[],
A31 extends unknown[], A32 extends unknown[], A33 extends unknown[],
A34 extends unknown[], A35 extends unknown[], A36 extends unknown[],
A37 extends unknown[], A38 extends unknown[], A39 extends unknown[],
A40 extends unknown[]
> = {
(...args: A01): unknown; (...args: A02): unknown; (...args: A03): unknown;
(...args: A04): unknown; (...args: A05): unknown; (...args: A06): unknown;
(...args: A07): unknown; (...args: A08): unknown; (...args: A09): unknown;
(...args: A10): unknown; (...args: A11): unknown; (...args: A12): unknown;
(...args: A13): unknown; (...args: A14): unknown; (...args: A15): unknown;
(...args: A16): unknown; (...args: A17): unknown; (...args: A18): unknown;
(...args: A19): unknown; (...args: A20): unknown; (...args: A21): unknown;
(...args: A22): unknown; (...args: A23): unknown; (...args: A24): unknown;
(...args: A25): unknown; (...args: A26): unknown; (...args: A27): unknown;
(...args: A28): unknown; (...args: A29): unknown; (...args: A30): unknown;
(...args: A31): unknown; (...args: A32): unknown; (...args: A33): unknown;
(...args: A34): unknown; (...args: A35): unknown; (...args: A36): unknown;
(...args: A37): unknown; (...args: A38): unknown; (...args: A39): unknown;
(...args: A40): unknown;
}
Es gibt ein kleines Problem mit den Lösungen im Issue-Thread, da sie nur funktionieren, wenn einer der Overloads entweder keine Parameter oder nur unbekannte Parameter verwendet, was bei on nicht der Fall ist. Die Lösung von jcalz hat dieses Problem nicht, würde aber erfordern, 41 * 40=1640 Funktionssignaturen zu schreiben, was unpraktisch ist.
Um das Problem zu beheben, können wir einfach diese zusätzliche Überladung hinzufügen, indem wir auf T & ((...args: unknown[]) => unknown) anstelle von T schließen und alle unknown[]-Elemente aus der resultierenden Liste entfernen.
type ElementTypeWithoutUnknown<T extends unknown[]> =
{[K in keyof T]: unknown[] extends T[K] ? never : T[K]}[number]
Jetzt haben wir alles, was wir brauchen, um GetOverloadParameters zu definieren:
type GetOverloadParameters<T extends (...args: any) => unknown> =
T & ((...args: unknown[]) => unknown) extends BuildOverloads<
infer A01, infer A02, infer A03, infer A04, infer A05, infer A06, infer A07,
infer A08, infer A09, infer A10, infer A11, infer A12, infer A13, infer A14,
infer A15, infer A16, infer A17, infer A18, infer A19, infer A20, infer A21,
infer A22, infer A23, infer A24, infer A25, infer A26, infer A27, infer A28,
infer A29, infer A30, infer A31, infer A32, infer A33, infer A34, infer A35,
infer A36, infer A37, infer A38, infer A39, infer A40
> ?
ElementTypeWithoutUnknown<[
A01, A02, A03, A04, A05, A06, A07, A08, A09, A10, A11, A12, A13, A14, A15,
A16, A17, A18, A19, A20, A21, A22, A23, A24, A25, A26, A27, A28, A29, A30,
A31, A32, A33, A34, A35, A36, A37, A38, A39, A40
]> : never
Und wir können es so anwenden:
type EventName = GetOverloadParameters<Electron.BrowserWindow['on']>[0]
// type EventName = "always-on-top-changed" | "app-command" | .. | "will-resize"
(Beachten Sie, dass der Typ Electron.BrowserWindow den Typ des Objekts und nicht die Klasse bezeichnet, sodass es nicht nötig ist, InstanceType<typeof Electron.BrowserWindow> zu verwenden.)
Da die Vereinigung von 36 Ereignisnamen zu groß ist, um in einem Hover-Typ angezeigt zu werden, können wir ein paar Hilfsfunktionen deklarieren, um sie basierend auf der Anzahl der Striche im Namen zu gruppieren (nur um zu überprüfen, dass sie alle vorhanden sind, Sie müssen dies in Ihrem Code nicht tun):
type Dash0<S extends string> = S extends `${string}-${string}` ? never : S
type Dash1<S extends string> = S extends `${string}-${Dash0<infer _>}` ? S : never
type Dash2<S extends string> = S extends `${string}-${Dash1<infer _>}` ? S : never
type Dash3<S extends string> = S extends `${string}-${Dash2<infer _>}` ? S : never
type EventName1 = Dash0<EventName>
// EventName1 = "blur" | "close" | "closed" | "focus" | "hide" | "maximize" |
// "minimize" | "move" | "moved" | "resize" | "resized" |
// "responsive" | "restore" | "show" | "swipe" | "unmaximize" |
// "unresponsive"
type EventName2 = Dash1<EventName>
// EventName2 = "app-command" | "rotate-gesture" | "session-end" | "sheet-begin" |
// "sheet-end" | "will-move" | "will-resize"
type EventName3 = Dash2<EventName>
// EventName3 = "enter-full-screen" | "leave-full-screen" | "page-title-updated" |
// "ready-to-show" | "scroll-touch-begin" | "scroll-touch-edge" |
// "scroll-touch-end" | "system-context-menu"
type EventName4 = Dash3<EventName>
// EventName4 = "always-on-top-changed" | "enter-html-full-screen" |
// "leave-html-full-screen" | "new-window-for-tab"
TypeScript-Spielplatz
Antwort #2
Kurzantwort: nicht möglich, siehe https://github.com/microsoft/TypeScript/issues/32164.
In Ihrem Fall empfehle ich, die Ereignistypen fest zu codieren.
Überladungen werden als Durchschnitte dargestellt, nicht als Vereinigungen. Die Überladungen in Vereinigungen umzuwandeln ist kompliziert, aber möglich. Ich empfehle nicht, meine Lösung zu verwenden. Der Typ funktioniert nur mit 1-50 Argumenten (die Dateigröße steigt schnell an, wenn man Unterstützung für mehr Argumente hinzufügt).
ParameterOfOverloads<electron.BrowserWindow['on']>[number][0]
https://gist.github.com/tscpp/e4d5f7b9cb27f58e71f4e2d788d4fd27
