我有两种功能类型,我希望它们是等效的,但它们的行为不同.
这些是类型:
type MatchT<T extends {id: string}, M> =
[M] extends [TyMap<infer X>]
? [T] extends [X]
? (t: T, m: M) => Ret<T, M>
: (t: T, m: M) => never
: (t: T, m: M) => never;
以及:
type MatchT<T extends {id: string}, M> = (t: T, m: M) =>
[M] extends [TyMap<infer X>]
? [T] extends [X]
? Ret<T, M>
: never
: never;
不同之处在于,在第一种情况下,整个函数类型位于条件内,而在第二种情况下,只有返回类型位于条件内.
MatchT
is used to re-type a function with type <T>(t: T, map: TyMap<T>): Ret<T, TyMap<T>>
into one with type <T, X>(t: T, map: TyMap<X>): Ret<T, TyMap<X>>
as long as T
is a subtype of X
. (These types aren't valid in TS, but they're short and give a good intuition on what I'm trying to do).
The first version works as I'd expect, while the second one fails and returns never
. Experimentally I noticed that [M] extends [TyMap<infer X>]
evaluates to false
, but only in the second version of MatchT
.
为什么这两种类型给我的结果不同?
您可以在操场上玩一个完全可行的示例:https://tsplay.dev/NVX7MW.命名空间test_1
使用我在这里发布的MatchT
的第一个版本,而test_2
使用第二个版本.否则您可以在这里找到示例代码:
type A = {id: "A", a: number}
type B = {id: "B", b: string}
type TyMap<T extends {id: string}> = {
[K in T['id']]: (x: T & {id: K})=>unknown
}
type Ret<T extends {id: string}, M extends TyMap<T>> = ReturnType<M[T['id']]>;
function match<T extends {id: string}, M extends TyMap<T>>(t: T, map: M): Ret<T, M> {
const f = (map as Record<string,unknown>)[t.id] as (x: T) => Ret<T, M>;
return f(t);
}
declare const a: A, ab: A | B;
namespace test_1 {
const matchT = <T extends {id: string}, M>(t: T, m: M) => {
return (match as MatchT<T, M>)(t, m);
}
type MatchT<T extends {id: string}, M> =
[M] extends [TyMap<infer X>]
? [T] extends [X]
? (t: T, m: M) => Ret<T, M>
: (t: T, m: M) => never
: (t: T, m: M) => never;
const wrap = <T extends A|B>(t: T) => {
return matchT(t, {A: a => a.a, B: b => b.b} satisfies TyMap<A|B>);
}
const a1 = wrap(a);
// ^?
const ab1 = wrap(ab);
// ^?
}
namespace test_2 {
const matchT = <T extends {id: string}, M>(t: T, m: M) => {
return (match as MatchT<T, M>)(t, m);
}
type MatchT<T extends {id: string}, M> = (t: T, m: M) =>
[M] extends [TyMap<infer X>]
? [T] extends [X]
? Ret<T, M>
: never
: never;
const wrap = <T extends A|B>(t: T) => {
return matchT(t, {A: a => a.a, B: b => b.b} satisfies TyMap<A|B>);
}
const a1 = wrap(a);
// ^?
const ab1 = wrap(ab);
// ^?
}