網站搜尋

typescript - 我可以在條件型別中保留泛型嗎?


假設我有一個條件型別,它會改變函數的返回型別:

type ChangeReturnType<F, R> =
    F extends (this: unknown, ...args: infer A) => any ?
        (...args: A) => R :
    F extends (this: infer T, ...args: infer A) => any ?
        (this: T, ...args: A) => R :
    F;

這對任何非通用函數或方法都適用。

function foo(a: number, b: string): void {}
type Foo = typeof foo;
// Foo: (a: number, b: string) => void;

type Bar = ChangeReturnType<typeof foo, undefined>;
// Bar: (a: number, b: string) => undefined;

interface IFoo {
    method1(a: number, b: string): void;
    method2(this: SomeClass, a: number): void;
}

type Method1 = ChangeReturnType<IFoo["method1"], undefined>;
type Method2 = ChangeReturnType<IFoo["method2"], undefined>;
// Method1: (a: number, b: string) => undefined;
// Method2: (this: SomeClass, a: number) => undefined;

我知道這些牽強的例子看起來很沒用,因為它們只是用來說明概念的簡短範例。若要一個更有意義的例子,即便仍然有些牽強,請參見此遊樂場連結。

然而,如果函數或方法具有泛型類型參數,它們會丟失,對應的函數參數會變成類型 unknown。

type ChangeReturnType<F, R> =
    F extends (this: unknown, ...args: infer A) => any ?
        (...args: A) => R :
    F extends (this: infer T, ...args: infer A) => any ?
        (this: T, ...args: A) => R :
    F;

function foo<T>(a: T, b: T): void {}
type Foo = typeof foo;
// Foo: <T>(a: T, b: T) => void;

type Bar = ChangeReturnType<Foo, undefined>;
// Bar: (a: unknown, b: unknown) => undefined;

interface IFoo {
    method1<T>(a: T, b: T): void;
    method2<T>(this: SomeClass, a: T): void;
    method3<T>(this: T, a: number): void;
}

type Method1 = ChangeReturnType<IFoo["method1"], undefined>;
type Method2 = ChangeReturnType<IFoo["method2"], undefined>;
type Method3 = ChangeReturnType<IFoo["method3"], undefined>;
// Method1: (a: unknown, b: unknown) => undefined;
// Method2: (this: SomeClass, a: unknown) => undefined;
// Method3: (a: number) => undefined;  <-- the this parameter gets lost entirely

當然,這僅僅是類型資訊,本身不會在運行時造成任何問題,但泛型及其隱式或顯式的約束的丟失,可能導致未捕獲的錯誤。

type ChangeReturnType<F, R> =
    F extends (this: unknown, ...args: infer A) => any ?
        (...args: A) => R :
    F extends (this: infer T, ...args: infer A) => any ?
        (this: T, ...args: A) => R :
    F;

function foo<T>(a: T, b: T): void {
    if (typeof a === typeof b)
        console.log(`okay: a and b are both of type "${typeof a}"`);
    else
        console.error(`error: a is "${typeof a}", but b is "${typeof b}"`);
}

foo(1, 1);
foo("a", "a");
// foo(1, "a");  <-- TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.

const bar = foo as ChangeReturnType<typeof foo, undefined>;
bar(1, 1);
bar("a", "a");
bar(1, "a"); // <-- No error from TS, but error at run time!

有什麼方法可以在像這樣的條件型別中保留泛型嗎?這似乎有點像是高階類型的工作,但在缺乏這個功能的情況下,有沒有某種替代方法可以讓我實現這個功能,即使是在有限的範圍內?