繁体   English   中英

在 TypeScript 映射类型中保留泛型

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM