Vyhledávání na webu

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.