Website-Suche

typescript - Wie kann ich ergonomische Typen für ein Modul bereitstellen, das standardmäßig ein Objekt exportiert, das eine Klasse enthält?


Mit „ergonomisch“ meine ich „erfordert nicht, dass Verbraucher die Syntax InstanceType<typeof mod["Foo"]> verwenden“

Ich schreibe eine .d.ts für eine JS-Quelldatei. In ein paar Monaten wird diese JS-Datei in TS konvertiert, aber wir können sie noch nicht konvertieren.

Die Datei sieht ungefähr so aus:

// module.js
class Foo {
    sayFoo(){}
}

export default {
    Foo
};

tsc erzeugt eine .d.ts wie folgt:

// module.d.ts
declare class Foo {
    sayFoo(): void;
}
declare const _default: {
    Foo: typeof Foo;
};
export default _default;

Der Anrufer muss durch einige wirklich seltsame Hürden springen, um an den Klassentyp zu gelangen:

import mod from "./module"

function takesFoo(foo: InstanceType<typeof mod["Foo"]>) {
    foo.sayFoo();
}

Aber wenn ich bei den Typen ein bisschen mit namespace lüge:

export default NS;
// module.d.ts
declare namespace NS {
    export class Foo {
        sayFoo(): void
    }
}

dann erhält der Aufrufer eine viel bessere Typ-Ebene-API!

import mod from "./module";
function takesFoo(foo: mod.Foo) {
    foo.sayFoo();
}

Ich könnte einfach namespace verwenden, aber dann stecken wir in der Klemme, wenn wir die JS-Datei in TS konvertieren. Unsere Optionen wären zu diesem Zeitpunkt:

  • (A) Konvertiere den Bibliothek-Entry-Point niemals tatsächlich zu TS. Schreibe den Großteil der Bibliothek in TS und verwende dann eine winzige JS-Datei mit einer .d.ts, um die API zu umschließen. Das würde jegliche Überprüfungen entfernen, ob wir tatsächlich unserer öffentlichen API entsprechen! Und es wäre verwirrend.

  • (B) Erzwingen Sie die seltsame API auf Typ-Ebene bei den Aufrufern. Vielleicht mildern Sie den Schlag, indem Sie einige Schnittstellen exportieren, und bringen Sie den Aufrufern die gespaltene Persönlichkeit von TS-Klassentypen bei.

Fehlt mir eine Option oder verstehe ich etwas über Typ-Ebenen-Punkte in TS nicht?

Antwort #1

AKTUALISIERUNG: Es könnte viel einfacher sein, als ich dachte, obwohl mein Rat, wie man Standard-Exporte verwendet, weiterhin gilt... Außerdem könnte dies tatsächlich nicht laufen (weitere Aktualisierung: Es scheint in Ordnung zu sein, wenn ich es in einer einfachen mit create-react-app erstellten Umgebung ausprobiere).

Mit Ihrem bestehenden Standard-Export scheint dies zu kompilieren:

import { Foo } from "./module"

const x = new Foo();

function f(y: Foo) {
   y.sayFoo();
}

f(x);

Originalantwort unten:

Hast du die Freiheit, export vor class Foo { zu setzen?

Auf diese Weise können Benutzer sagen:

import { Foo } from "./module";

und dann Foo direkt verwenden:

function takesFoo(foo: Foo) {

Alternativ, wenn Foo das einzige aus dem Modul exportierte Element ist und Sie möchten, dass es der Standardexport ist, würde es mehr Sinn machen, es alleine zu exportieren, anstatt es in einem Objekt zu verpacken:

export default Foo; 

damit Benutzer es importieren könnten:

import Foo from "./module";

Die Art, wie ich an den default-Export denke, ist, dass er am sinnvollsten ist, wenn ich nur eine Sache zu exportieren habe, in welchem Fall, warum sollte ich sie in einem Objekt einwickeln? Und wenn ich mehrere Dinge zu exportieren habe, exportiere ich sie einfach einzeln, sodass sie bei Bedarf mit { ... } importiert werden können.