如何缩小TypeScript索引访问类型的范围?
问题
我定义了Queries类型来管理不同查询的参数与返回值类型:
type Queries = { query1: { args: { id: number }, return: number }, query2: { args: { type: string }, return: string }, query3: { args: { world: "hello" }, return: "world" } }
接着用它约束泛型函数query,调用时类型推断正常:
type QueryTypes = keyof Queries function query<T extends QueryTypes>(type: T, args: Queries[T]["args"]) { return something as Queries[T]["return"] } const result1 = query("query1", { id: 0 }) // result1: number const result2 = query("query2", { type: "hi" }) // result2: string const result3 = query("query3", { world: "hello" }) // result3: "world"
但在函数内部通过switch判断type时,args的类型无法自动收窄,始终是所有参数类型的联合:
switch(type){ case "query1": // args 类型为 { id:number } | { type:string } | { world: "hello" } break }
希望实现的是,在switch的不同分支中,args自动对应到当前type的具体参数类型:
switch(type){ case "query1": // args: { id:number } break case "query2": // args: { type:string } break }
解决方案
问题出在泛型函数的参数关联方式上:泛型T只是在调用阶段约束type和args的对应关系,但在函数内部,TypeScript无法把泛型T和switch分支的具体type值绑定,因此无法收窄args的类型。
要解决这个问题,需要让type和args形成强绑定的联合类型,这样TypeScript就能通过switch分支自动推断对应类型:
步骤1:定义绑定查询类型与参数的联合元组
type QueryTuple = { [K in keyof Queries]: [type: K, args: Queries[K]["args"]] }[keyof Queries]
这个类型会把每个查询的type和对应的args打包成元组,最终生成一个包含所有合法组合的联合类型。
步骤2:重构query函数
用上述联合元组作为函数参数,此时switch分支里的args类型会自动收窄:
function query(...[type, args]: QueryTuple) { switch(type) { case "query1": // args 自动收窄为 { id: number } console.log(args.id) // 类型安全,无报错 return 123 as Queries["query1"]["return"] case "query2": // args 自动收窄为 { type: string } console.log(args.type) return "abc" as Queries["query2"]["return"] case "query3": // args 自动收窄为 { world: "hello" } console.log(args.world) return "world" as Queries["query3"]["return"] default: throw new Error(`Unknown query type: ${type}`) } }
可选:保留泛型调用方式
如果需要保留原有的泛型调用能力(比如显式指定泛型参数),可以给函数添加重载签名:
// 重载签名:支持泛型调用 function query<T extends keyof Queries>(type: T, args: Queries[T]["args"]): Queries[T]["return"] // 实现签名:用联合元组实现类型收窄 function query(...[type, args]: QueryTuple) { switch(type) { case "query1": return 123 as Queries["query1"]["return"] case "query2": return "abc" as Queries["query2"]["return"] case "query3": return "world" as Queries["query3"]["return"] default: throw new Error(`Unknown query type: ${type}`) } } // 两种调用方式均正常工作 const result1 = query("query1", { id: 0 }) // result1: number const result2 = query<"query2">("query2", { type: "hi" }) // result2: string
这样既保证了函数内部的类型安全,又兼容了原有的调用方式。
内容的提问来源于stack exchange,提问作者codebroccoli
相关产品推荐
相关产品推荐

