TypeScript hur man skapar en generisk typalias för en generisk funktion?
Givet en typad generisk funktion vill jag skapa en generisk alias för den funktionen, men det verkar som att jag inte kan. Med andra ord fungerar inte detta:
// foo.tsx
function foo<T>(arg: T): T {
return arg
}
type FooT = typeof foo // works, but alias isn't generic: <T>(arg: T) => T
type FooParametersT = Parameters<typeof foo> // sure: [unknown]
type FooReturnT = ReturnType<typeof foo> // no problem: unknown
type GFooT<T,> = typeof foo<T,> // yikes
type GFooParametersT<T,> = Parameters<typeof foo<T,>> // nope
type GFooReturnT<T,> = ReturnType<typeof foo<T,>> // fails
Det jag egentligen försöker göra är att ta typ av en funktion i någon annans bibliotek och sedan bygga ett gränssnitt runt den. Till exempel:
import { useState } from "react"
type UseStateFnT<T,> = typeof useState<T>
const wrapUseState: (toWrap: UseStateFnT<T,>) => UseStateFnT<T,> = …
Är detta möjligt utan att jag själv skapar den komplexa typade funktionssignaturen?
Svar #1
UPPDATERING FÖR TS4.7+
Hej igen! TypeScript 4.7 kommer att introducera instansieringsexpressioner som implementerat i microsoft/TypeScript#47607, vilket kommer att låta dig direkt ange typparametrarna för en generisk funktion utan att faktiskt kalla på funktionen, så det finns inte längre något behov av att missbruka klasser på det sätt som visas i resten av svaret. Här är det:
type GFooT<T,> = typeof foo<T> // (arg: T) => T
type GFooParametersT<T,> = Parameters<typeof foo<T>> // [arg: T]
type GFooReturnT<T,> = ReturnType<typeof foo<T>> // T
Det är nästan samma som syntaxen i frågan, förutom att avslutande kommatecken inte stöds efter typargument (men de stöds efter typparameternamn). Hurra!
Lekplatslänk till kod
TIDIGARE SVAR FÖR TS4.6-
Det finns två olika varianter av generiska i TypeScript: generiska funktioner och generiska typer... och det verkar som att du vill att kompilatorn ska omvandla den ena till den andra åt dig, vilket inte stöds direkt.
För tydlighetens skull:
Generiska typer har typparametrar som måste specificeras innan du kan använda dem som en specifik typ. Till exempel:
type GenType<T> = (x: T) => T[];
declare const oops: GenType; // error
declare const genT: GenType<string>; // okay
const strArr = genT("hello"); // string[];
const numArr = genT(123); // error!
Här är GenType en generisk typ. Du måste ange typparametern för att använda den som typen för ett värde, och då är den resulterande typen inte längre generisk. Funktionen genT tar en string och returnerar en string[]. Den kan inte användas som en funktion som tar ett number och returnerar ett number[].
Generiska funktioner, å andra sidan, har en specifik typ som kan agera som vilken möjlig ersättning av dess typparametrar som helst. Värdet av en generisk funktionstyp är fortfarande generiskt när du använder det. Typparametern är kopplad till anropssignaturen:
type GenFunc = <T>(x: T) => T[];
declare const genF: GenFunc;
const strArr = genF("hello"); // strArr: string[];
const numArr = genF(123); // numArr: number[];
Här är GenFunc en specifik typ som hänvisar till en generisk funktion. genF-funktionen är fortfarande generisk när den anropas.
Generiska funktioner (inklusive generiska konstruktionsfunktioner) kan betraktas som generiska värden, till skillnad från generiska typer.
Dessa två typer av generics är relaterade till varandra, men TypeScript-typ-systemet är inte tillräckligt uttrycksfullt för att kunna beskriva hur de är relaterade. I något annat språk kanske du skulle kunna definiera den ena i termer av den andra som
type GenFunc = forall T, GenType<T>; // not TS, error
eller
type GenType<T> = instantiate GenFunc with T; // not TS, error
men i TypeScript kan du inte. Kanske om vi någonsin får högre ordningens typer som begärt i microsoft/TypeScript#1213... men inte nu. Så det är inte direkt möjligt att omvandla GenFunc till GenType programmatiskt i typ-systemet.
Det finns onda hemska sätt att tvinga kompilatorn att beräkna GenType i termer av GenFunc. Sättet jag känner till använder sig av initialisering av egenskaper i generiska klasser och viss högre ordningens typinferenz för generiska funktioner som introducerades i TypeScript 3.4. Jag får kompilatorn att tro att den beräknar värden när jag egentligen inte har några, och sedan får jag typen för ett av dessa påhittade värden:
class GenTypeMaker<T> {
getGenType!: <A extends any[], R>(cb: (...a: A) => R) => () => (...a: A) => R;
genType = this.getGenType(null! as GenFunc)<T>()
}
type GenType2<T> = GenTypeMaker<T>['genType']
// type GenType2<T> = (x: T) => T[]
Du kan verifiera att GenType2<T> är samma typ som GenType<T>, och om du ändrar GenFunc till vilken generisk funktion som helst med en typparameter, kommer GenType2<T> att ändras därefter.
Men jag vet inte om jag verkligen skulle vilja rekommendera någon att faktiskt använda denna metod. Och den skalar inte riktigt eller kan kombineras; om du har ett objekt fullt av generiska funktioner i en typparameter och du vill omvandla det till ett objekt fullt av specifika funktioner med en specificerad typparameter, finns det inget sätt att använda denna metod för att få det från typsystemet utan att göra det en gång för varje objektattribut.
Lekplatslänk till kod
