如何让TypeScript基于泛型布尔值收窄泛型条件类型?
如何让TypeScript基于泛型布尔值收窄泛型条件类型?
嘿,我太懂你遇到的这个TypeScript类型收窄的难题了!当你想用泛型布尔值M来控制条件类型TypeValue的时候,TypeScript默认没法自动根据multiple的实际值来智能收窄对应的类型,对吧?我之前踩过一模一样的坑,给你分享几个亲测好用的解决办法:
方法一:函数重载——明确输入输出的类型映射
这是最直接的解决方案,通过给函数定义多个重载签名,提前告诉TypeScript不同输入对应的输出类型:
type TypeValue<T, M> = M extends true ? T[] : T; type ArgsTest<T, M> = { value: TypeValue<T, M>; multiple: M; }; // 先定义两个重载签名,明确不同multiple值对应的类型 function test<T extends { id: number }>(args: ArgsTest<T, false>): T; function test<T extends { id: number }>(args: ArgsTest<T, true>): T[]; // 然后是函数的实现签名 function test<T extends { id: number }, M extends boolean>(args: ArgsTest<T, M>): TypeValue<T, M> { if (args.multiple) { // 这里可以放心把value当成数组处理,重载已经帮我们约束了类型对应关系 return args.value as T[]; } else { // 这里就是单个T类型 return args.value as T; } } // 测试一下,完全符合预期 const singleResult = test({ value: { id: 1 }, multiple: false }); // 类型是 { id: number } const arrayResult = test({ value: [{ id: 1 }, { id: 2 }], multiple: true }); // 类型是 { id: number }[]
重载的好处是能保留你原来的ArgsTest结构,调用函数时TypeScript会自动匹配对应的重载,实现里的类型断言是完全安全的,因为重载已经帮我们把输入和输出的类型绑定死了。
方法二:自定义类型守卫——手动帮TS收窄类型
如果不想用重载,你可以写一个自定义的类型守卫函数,把multiple的布尔值和对应的value类型关联起来,让TypeScript能智能识别:
type TypeValue<T, M> = M extends true ? T[] : T; type ArgsTest<T, M> = { value: TypeValue<T, M>; multiple: M; }; // 自定义类型守卫,判断multiple是否为true,同时告诉TS对应的完整类型 function isMultiple<T, M extends boolean>(args: ArgsTest<T, M>): args is ArgsTest<T, true> { return args.multiple === true; } const test = <T extends { id: number }, M extends boolean>(args: ArgsTest<T, M>): TypeValue<T, M> => { if (isMultiple(args)) { // 这里TS会自动知道args.value是T[]类型,直接调用数组方法都没问题 args.value.forEach(item => console.log(item.id)); return args.value; } else { // 这里自动是单个T类型,直接访问id属性完全安全 console.log(args.value.id); return args.value; } };
这个类型守卫相当于给TypeScript递了个“提示卡”,告诉它当multiple为true时,整个参数的类型就是ArgsTest<T, true>,对应的value自然就是数组类型,完全不需要额外的类型断言。
方法三:可辨识联合类型——最安全直观的写法
我个人最推荐这个方案,把原来的条件类型拆成两个明确的子类型,用multiple作为辨识符,TypeScript对这种结构的类型收窄支持非常好:
// 拆成两个明确的类型,用multiple作为辨识符 type SingleArgs<T> = { value: T; multiple: false; }; type MultipleArgs<T> = { value: T[]; multiple: true; }; // 合并成可辨识联合类型 type ArgsTest<T> = SingleArgs<T> | MultipleArgs<T>; const test = <T extends { id: number }>(args: ArgsTest<T>): T | T[] => { if (args.multiple) { // TS自动收窄到MultipleArgs<T>,value是T[]类型 return args.value; } else { // TS自动收窄到SingleArgs<T>,value是T类型 return args.value; } };
这种写法完全抛弃了条件类型的模糊性,每个类型分支都非常明确,TypeScript能自动根据multiple的值精准收窄类型,代码既安全又好读,几乎没什么心智负担。
总的来说,如果你想保留原来的条件类型结构,函数重载或类型守卫都可以;如果追求代码的清晰性和安全性,可辨识联合类型绝对是最优解。
内容来源于stack exchange
相关产品推荐
相关产品推荐

