TypeScript: gör en metod av ett generiskt gränssnitt A<T> tillgänglig endast om T är av en viss typ


Var vänlig och titta på denna kod:

interface A<T> {
    method1(): A<T>;
}

interface B<T extends Function> extends A<T> {
    method2(): B<T>;
}

var foo: A<Function>;
foo.method1();
foo.method2();

Jag förväntade mig att foo skulle vara kompatibel med typen B<Function>, men istället får jag error TS2339: Property 'method2' does not exist on type 'A<Function>'. Kan jag på något sätt skriva om gränssnittet B för att få det att fungera?

Faktiskt försöker jag fixa typningarna för Lodashs _.memoize:

// This should be OK. The type of result1 should be compatible with aFunction.
var result1 = _(aFunction).memoize().value();

// And this should be an error.
var result2 = _(aNonFunctionValue).memoize().value();

UPPDATERING. I princip är min fråga: kan jag skriva en sådan generisk metod för A<T> så att den endast är tillgänglig om T är en underklass av någon annan typ U?

Svar #1

Kan jag skriva om gränssnittet B på något sätt för att få det att fungera?

Lösning

Här är koden:

interface A<T> {
    method1(): A<T>;
}

interface B<T extends Function> extends A<T> {
    method2(): B<T>;
}

interface A<T> {
    method2<T extends Function>(): B<T>;
}

var foo: A<Function>;
foo.method1();
foo.method2(); // works!

Komma fram till lösningen

Låt oss ta ett steg tillbaka och fundera på vad vi vill arbeta med:

var foo: A<Function>;
foo.method2(); // Should work

Detta betyder att A<Function> bör ha method2 på sig. Så:

interface A<T> {
    method2<T extends Function>(): B<T>;
}

Och denna metod två lägger till en generell begränsning på T och returnerar något av typen B.

Resten är klar i den slutliga lösningen 🌹

Svar #2

Visade sig att TypeScript inte stödjer detta. Se https://github.com/Microsoft/TypeScript/issues/1290

UPPDATERING 2019: Nu när vi har villkorstyper är det en barnlek:

interface A<T> {
    method1(): A<T>;
    method2: T extends Function ? () => A<T> : never;
}

declare var foo: A<Function>;
foo.method1();
foo.method2();