TypeScript泛型处理T|T[]|"*"时展开运算符类型推导异常排查
我是TypeScript泛型新手,对这段代码里的展开运算符...工作异常感到困惑,想请教怎么让它正常工作。
问题所在
代码行[ key: U, ...rest: Registery[U] ]的类型表现不符合预期:当传入数组类型的key(比如["item.1", "item.2"])时,回调函数里的args类型没有根据key的具体值做正确区分,所有参数都变成了联合类型,无法在条件分支里得到准确的类型推断。
我的疑问
我哪里出错了?尝试调整代码但没能解决问题。
尚未尝试的可能方案
- 函数重载
原代码
type Registery = { "item.1": [name: string] "item.2": [id: number, ready: "Y" | "N"] "item.3": [ok: boolean] } type ReK = keyof Registery const send = < T extends ReK | ReK[] | "*" | never = never >( key: T, callback: ( ...args: T extends Array<infer U> ? ( U extends ReK ? [ key: U, ...rest: Registery[U] ] : never ) : T extends string ? ( T extends ReK ? Registery[T] : T extends "*" ? Registery[ReK] : never ) : never ) => any ) => { /** */ } send("item.2", (...args) => { const [ arg1, //^?const arg1: number arg2, //^?const arg2: "Y" | "N" ] = args }) send(["item.1", "item.2"], (key, ...args) => { // ^? const k = key // ^?const k: "item.1" | "item.2" if (key == "item.1") { const [ arg1, //^?const arg1: string | number arg2, //^?const arg1: string | number ] = args } if (key == "item.2") { const [ arg1, //^?const arg1: string | number arg2, //^?const arg2: string | number ] = args } })
问题原因
当传入数组类型的key时,TypeScript推断出U是"item.1" | "item.2"的联合类型,此时[key: U, ...rest: Registery[U]]会被解析为每个位置都是联合类型的元组,而不是多个元组的联合。这就导致在if (key == "item.1")这类分支里,TypeScript无法将args的类型窄化为对应key的专属元组,只能保留宽泛的联合类型。
解决方案
要让TypeScript正确生成元组的联合类型,需要利用分布式条件类型——让条件类型对联合类型的每个成员单独处理,而非将整个联合类型代入计算。我们可以把参数类型逻辑抽成单独的类型别名,确保U作为裸类型参数被处理:
type Registery = { "item.1": [name: string] "item.2": [id: number, ready: "Y" | "N"] "item.3": [ok: boolean] } type ReK = keyof Registery // 抽离回调参数的类型逻辑,利用分布式条件类型生成元组联合 type CallbackArgs<T> = T extends (infer U)[] ? U extends ReK ? [key: U, ...rest: Registery[U]] : never : T extends string ? ( T extends ReK ? Registery[T] : T extends "*" ? Registery[ReK] : never ) : never; const send = < T extends ReK | ReK[] | "*" | never = never >( key: T, callback: (...args: CallbackArgs<T>) => any ) => { /** */ } // 单个key的情况:类型推断正常 send("item.2", (...args) => { const [ arg1, // ^? const arg1: number arg2 // ^? const arg2: "Y" | "N" ] = args }) // 数组key的情况:分支内类型推断正确 send(["item.1", "item.2"], (key, ...args) => { const k = key // ^? const k: "item.1" | "item.2" if (key == "item.1") { const [ arg1 // ^? const arg1: string ] = args // 此时args被正确窄化为 ["item.1", string],无多余参数提示 } if (key == "item.2") { const [ arg1, // ^? const arg1: number arg2 // ^? const arg2: "Y" | "N" ] = args } })
修改后,当传入数组["item.1", "item.2"]时,CallbackArgs<T>会被解析为["item.1", string] | ["item.2", number, "Y" | "N"],TypeScript就能在条件分支里根据key的值正确窄化args的类型了。
你提到的函数重载也是可行方案,但对于这种需要处理联合类型的场景,利用分布式条件类型的泛型方案会更简洁。
内容的提问来源于stack exchange,提问作者azzmi

