元组成员的打字稿类型解析 - 为什么要编译?

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")

这对编译器来说没问题——我本以为第二个参数的类型会解析为 neverundefined.

那么,我的问题是

为什么对代表无效类型的值的“匹配”调用成功?

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。否则,您的论点的约束就不相关了。

(编辑:更新为更接近原始代码的简化解决方案)