typescript - Jak mohu poskytnout ergonomické typování pro modul, který defaultně exportuje objekt obsahující třídu?
Pod pojmem „ergonomický“ mám na mysli „nevyžaduje, aby spotřebitelé používali zápis
InstanceType<typeof mod["Foo"]>“
Píšu .d.ts pro JS zdrojový soubor. Za několik měsíců bude ten JS soubor převeden na TS, ale zatím ho nemůžeme převést.
Soubor vypadá nějak takto:
// module.js
class Foo {
sayFoo(){}
}
export default {
Foo
};
tsc generuje .d.ts takto:
// module.d.ts
declare class Foo {
sayFoo(): void;
}
declare const _default: {
Foo: typeof Foo;
};
export default _default;
Volající musí projít několika opravdu zvláštními překážkami, aby získal typ třídy:
import mod from "./module"
function takesFoo(foo: InstanceType<typeof mod["Foo"]>) {
foo.sayFoo();
}
Ale pokud trochu lžu o typech pomocí namespace:
export default NS;
// module.d.ts
declare namespace NS {
export class Foo {
sayFoo(): void
}
}
pak volající získá mnohem lepší API na úrovni typů!
import mod from "./module";
function takesFoo(foo: mod.Foo) {
foo.sayFoo();
}
Mohl bych prostě použít namespace, ale pak budeme v úzkých, když převedeme JS soubor na TS. Naše možnosti v té chvíli budou:
(A) Nikdy ve skutečnosti nepřevádějte vstupní bod knihovny na TS. Většinu knihovny napsat v TS, pak použít malý JS soubor s .d.ts pro zabalení API. To by odstranilo jakékoliv kontroly, že skutečně dodržujeme naše veřejné API! A bylo by to matoucí.
(B) Nutit nezvyklé API na úrovni typů na volající. Možná zmírnit dopad exportováním některých rozhraní a naučit volajícího o dvojí povaze typů tříd v TypeScriptu.
Chybí mi nějaká možnost, nebo něčemu ohledně teček na úrovni typů v TS nerozumím?
Odpověď č. 1
AKTUALIZACE: Může to být mnohem jednodušší, než jsem si myslel, i když moje rada ohledně používání výchozích exportů stále platí... Také to nemusí skutečně fungovat (další aktualizace: zdá se, že je to v pořádku, pokud to zkusím v jednoduchém prostředí generovaném create-react-app).
S vaším stávajícím výchozím exportem se zdá, že se to kompiluje:
import { Foo } from "./module"
const x = new Foo();
function f(y: Foo) {
y.sayFoo();
}
f(x);
Původní odpověď níže:
Máte svobodu přidat export před class Foo {?
Tímto způsobem mohou uživatelé říct:
import { Foo } from "./module";
a pak použijte Foo přímo:
function takesFoo(foo: Foo) {
Alternativně, pokud je Foo jedinou věcí exportovanou z modulu a chcete, aby byla výchozím exportem, dávalo by větší smysl ji exportovat samostatně, než ji obalovat do objektu:
export default Foo;
tak aby si ho uživatelé mohli importovat:
import Foo from "./module";
Když přemýšlím o default exportu, přijde mi nejlogičtější, pokud mám exportovat jen jednu věc, v takovém případě proč ji obalovat do objektu? A pokud mám více věcí k exportu, prostě je exportuji jednotlivě, aby je bylo možné importovat s { ... } podle potřeby.
