保留TypeScriptMap类型中的泛型

dpiehjr4  于 2022-12-24  发布在  TypeScript
关注(0)|答案(1)|浏览(291)

我有一个类,类的示例方法是句柄,每个句柄代表一个操作,取一个引用作为输入,把输出赋给第二个参数,由第三方库生成一个代理对象,这样就可以直接调用句柄。

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

但是,当处理程序涉及泛型类型时,Map器不起作用,例如,

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 },
}

有什么办法可以做到这一点吗?

jexiocij

jexiocij1#

这在TypeScript中目前是不可能的。为了在类型级别表达您对generic函数所做的操作,您需要microsoft/TypeScript#1213中所要求的 * 更高类的类型 *,但这些类型不直接受支持。Mapper<Fn>conditional type定义将U推断为输入类型,将V推断为输出类型。但是当Fn是通用的时,编译器没有能力看到或表示它们之间的任何高阶关系。
对于将泛型函数类型转换为其他泛型函数类型,有一些支持,但这只发生在非常特殊的情况下。转换至少需要部分发生在值级别(必须存在当被调用时将一个泛型函数类型转换成另一个泛型函数类型的函数 * value *,而不仅仅是那个函数的 * type *),所以会有JS代码发出。而且转换一次只对一个函数起作用,所以一个函数的对象不能一次Map而不丢失泛型。
尽管如此,为了证明我们有能力做到这一点,我们可以这样做:

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,如果你已经有了一个来自第三方的转换对象,并且只想表示类型,你能得到的最接近的方法是通过类型Assert欺骗编译器,这样它就认为你在做转换,而实际上你并没有:

// 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; };
} */

我不认为这是一个很大的胜利,只是写出来这些类型摆在首位,所以我不知道我会推荐它。这只是我可以想象得到的最接近你想要的语言,因为它是目前。
Playground代码链接

相关问题