[英]Typescript: Narrowing down mapped types does not work with generics
[英]Preserving generics in TypeScript mapped types
我有一個類,它的實例方法是處理程序,每個處理程序代表一個操作,將引用作為輸入並將輸出分配給第二個參數。 代理對象由第三方庫生成,以便可以直接調用處理程序。
type InputRef<T> = {
current: T,
};
type OutputRef<T> = {
current?: T,
};
class Original {
increment(input: InputRef<number>, output: OutputRef<number>) {
const { current: inValue } = input;
output.current = inValue + 1;
}
}
type Mapper<Fn> = Fn extends (input: InputRef<infer U>, output: OutputRef<infer V>) => unknown ? (input: U) => V : never;
type MyProxyGeneratedByThirdPartyJs = { [FnName in keyof Original]: Mapper<Original[FnName]> };
declare const proxy: MyProxyGeneratedByThirdPartyJs;
const result = proxy.increment(3); // 4
但是,當處理程序涉及泛型類型時,映射器不起作用,例如,
class Original {
toBox<T>(input: InputRef<T>, output: OutputRef<{ boxed: T }>) {
const { current: inValue } = input;
output.current = { boxed: inValue };
}
}
使用上面相同的方式, proxy
的類型只涉及unknown
,而通用信息T
丟失了。
理想情況下,我希望proxy
是類型
{
toBox<T>(input: T): { boxed: T },
}
代替
{
toBox(input: unknown): { boxed: unknown },
}
有什么辦法可以做到這一點?
這在 TypeScript 中目前是不可能的。 為了在類型級別表達您對泛型函數所做的操作,您需要更高種類的類型,如microsoft/TypeScript#1213中所要求的,但這些類型不受直接支持。 Mapper<Fn>
的條件類型定義將U
推斷為輸入類型,將V
推斷為輸出類型,但當Fn
是泛型時,編譯器無法查看或表示它們之間的任何高階關系。
有一些支持將泛型函數類型轉換為其他泛型函數類型,但這只發生在非常特殊的情況下。 轉換至少需要部分發生在值級別(必須有一個函數值在調用時將一種通用函數類型轉換為另一種類型,而不僅僅是該函數的類型),因此將發出 JS 代碼。 並且轉換一次只適用於單個函數,因此在不丟失泛型的情況下不能一次映射一個函數對象。
盡管如此,為了表明有一定的能力做到這一點,這里是一個可能的方法:
function proxySingleFunction<U, V>(
f: (input: InputRef<U>, output: OutputRef<V>) => any
): (input: U) => V {
return function (input: U) {
const o: OutputRef<V> = {};
f({ current: input }, o);
const ret = o.current;
if (ret === undefined) throw new Error("OH NO");
return ret;
}
}
proxySingleFunction()
的類型是
// function proxySingleFunction<U, V>(
// f: (input: InputRef<U>, output: OutputRef<V>) => any
// ): (input: U) => V
這看起來與您使用Mapper<Fn>
所做的類似。 然后,如果您調用proxySingleFunction()
,它將產生相關類型的輸出:
const increment = proxySingleFunction(Original.prototype.increment);
// const increment: (input: number) => number
const toBox = proxySingleFunction(Original.prototype.toBox);
// const toBox: <T>(input: T) => { boxed: T; }
您可以看到toBox
是通用的,正如所希望的那樣。 然后您可以將這些輸出函數打包到一個proxy
對象中並使用它:
const proxy = {
increment, toBox
}
console.log(proxy.increment(1).toFixed(1)) // "2.0"
console.log(proxy.toBox("a").boxed.toUpperCase()) // "A"
所以這很好,而且很管用。 但它要求您為每個要轉換的方法發出 JavaScript。 如果您已經從第三方獲得了這樣一個轉換后的對象,並且只想表示類型,那么您最接近的方法就是通過類型斷言對編譯器撒謊,這樣它就認為您在進行轉換,而實際上您並沒有這樣做:
// pretend this function exists
declare const psf: <U, V>(
f: (input: InputRef<U>, output: OutputRef<V>) => any
) => (input: U) => V;
// pretend you're using it to create a proxy object
const myProxyType = (true as false) || {
increment: psf(Original.prototype.increment),
toBox: psf(Original.prototype.toBox)
}
// get the pretend type of that object
type MyProxyGeneratedByThirdPartyJs = typeof myProxyType;
/* type MyProxyGeneratedByThirdPartyJs = {
increment: (input: number) => number;
toBox: <T>(input: T) => { boxed: T; };
} */
我不認為這比一開始就手動寫出這些類型有很大的好處,所以我不知道我會推薦它。 這只是我能想象到的最接近您想要的語言的當前狀態。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.