Typescript 元组成员的类型解析 - 为什么这能编译?
操场
你可以在这里找到带有代码的游乐场
给定以下类型定义:
type State = ["IDLE"] | ["STARTING_NEW_CONVERSATION"] | ["CHANNEL_ACTIVE", "SYNCED" | "EXPLORING"];
const state: State = ...
以及以下辅助方法:
export function matches<T extends [string] | [string, string], X = T[0], Y = T[1]>(
value: T,
check: T[0],
check2?: Extract<T, [X, Y]>[1]
) {
return value[0] === check && (check2 ? value[1] === check2 : true);
}
// another variation...
export function matches<T extends [string] | [string, string]>(
value: T,
check: T[0],
check2?: T extends [infer X, infer Y] ? Extract<T, [X, Y]>[1] : never
) {
return value[0] === check && (check2 ? value[1] === check2 : true);
}
它大多数情况下有效——不过,我可以这么称呼它
matches(state, "IDLE", "EXPLORING")
对于编译器来说这是可以的——我本来预期第二个参数的类型会被解析为 never 或 undefined。
- 值
["IDLE", "EXPLORING"]不能赋给state - 正确编写
Extract<State, ["IDLE", "EXPLORING"]>会提取never
所以,我的问题是
为什么对“matches”的调用使用表示无效类型的值会成功?
答案#1
TypeScript 不会根据你传入第二个参数的值来缩小 T 的类型。
给出这段代码:
type State = ["IDLE"] | ["STARTING_NEW_CONVERSATION"] | ["CHANNEL_ACTIVE", "SYNCED" | "EXPLORING"];
const state: State = {} as any
export function matches<T extends [string] | [string, string], X = T[0], Y = T[1]>(
value: T,
check: T[0],
check2?: Extract<T, [X, Y]>[1]
) {
return value[0] === check && (check2 ? value[1] === check2 : true);
}
matches(state, "IDLE", "EXPLORING")
TypeScript 推断以下参数类型:
value: State
check: "IDLE" | "STARTING_NEW_CONVERSATION" | "CHANNEL_ACTIVE"
check2: "SYNCED" | "EXPLORING"
当你将 "IDLE" 作为第二个参数传入时,TS 只是看了一眼,然后说“是的,这匹配 "IDLE" | "STARTING_NEW_CONVERSATION" | "CHANNEL_ACTIVE"”,然后继续。它不会收窄任何内容。
如果你需要根据第二个和第三个参数之间的关系来强制类型约束,那么你应该在模板参数列表中放置一个你要约束的公共类型,并从两个参数中引用它:
function matches<T extends State, X extends T[0], Y extends T[1]>(
value: T,
check: X,
check2?: Extract<T, [X, Y]>[1],
) {
return value[0] === check && (check2 ? value[1] === check2 : true);
}
type State = ["IDLE"] | ["STARTING_NEW_CONVERSATION"] | ["CHANNEL_ACTIVE", "SYNCED" | "EXPLORING"];
const state: State = {} as any
matches(state, 'IDLE', ) // WORKS
matches(state, 'CHANNEL_ACTIVE', 'EXPLORING') // Works
matches(state, 'IDLE', 'EXPLORING') // ERROR: Argument of type '"EXPLORING"' is not assignable to parameter of type 'undefined'.
关键区别在于,我们在模板列表中定义类型X,然后在两个参数约束中使用它。这迫使TS推断出一个满足两个参数的单一类型X。如果没有这样做,你的参数约束彼此之间就没有关联。
(编辑:更新为一个更简化的解决方案,更接近你原来的代码)
