![](/img/trans.png)
[英]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.