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