簡體   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