元组成员的打字稿类型解析 - 为什么要编译?
Typescript Type resolution of tuple members - why does this compile?
游乐场
You can find a playground with the code here
给定以下类型定义:
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
那么,我的问题是
为什么对代表无效类型的值的“匹配”调用成功?
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
,然后在参数约束的both中使用它。这迫使 TS 推断出满足两个参数的单一类型 X
。否则,您的论点的约束就不相关了。
(编辑:更新为更接近原始代码的简化解决方案)
游乐场
You can find a playground with the code here
给定以下类型定义:
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
那么,我的问题是
为什么对代表无效类型的值的“匹配”调用成功?
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
,然后在参数约束的both中使用它。这迫使 TS 推断出满足两个参数的单一类型 X
。否则,您的论点的约束就不相关了。
(编辑:更新为更接近原始代码的简化解决方案)