Typescript元组成员的类型解析-为什么要编译? 为什么用表示无效类型的值成功调用“ matches”?

问题描述

游乐场

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,check2?: T extends [infer X,infer Y] ? Extract<T,Y]>[1] : never
) {
  return value[0] === check && (check2 ? value[1] === check2 : true);
}

主要有效-但是,我可以这样称呼它

matches(state,"IDLE","EXPLORING")

对于编译器来说这是可以的-我希望第二个参数的类型可以解析为neverundefined

  • ["IDLE","EXPLORING"]无法分配给state
  • 正确书写Extract<State,["IDLE","EXPLORING"]>提取never

所以我的问题是

为什么用表示无效类型的值成功调用“ matches”?

解决方法

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,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,'EXPLORING') // ERROR: Argument of type '"EXPLORING"' is not assignable to parameter of type 'undefined'.

主要区别在于我们在模板列表中定义类型X,然后在参数约束的 both 中使用它。这迫使TS推断满足两个参数的单个类型X。否则,您的参数约束将不相关。

(编辑:已更新为简化的解决方案,与您的原始代码非常接近)