如何为zip函数添加类型,保留Validated元组的位置类型?
问题描述
已知存在如下TypeScript类型定义:
type Valid<T> = { isValid: true, value: T }; type Invalid = { isValid: false }; type Validated<T> = Valid<T> | Invalid; const valid = <T>(value: T): Valid<T> => ({ isValid: true, value }); const invalid: Invalid = { isValid: false };
需要为下面的zip函数添加类型注解,使其返回值的元组结构与传入的参数列表结构完全一致:
const zip = (...items) => items.every(item => item.isValid) ? { isValid: true, value: items.map(item => item.value) } : { isValid: false };
期望效果:
const result = zip(valid(1), valid("a"), valid("b"), valid(true)); // result类型应为 Valid<[number, string, string, boolean]> | Invalid;
我曾尝试过以下写法,但会丢失元组的位置类型信息,返回类型变成Valid<(number | string | boolean)[]> | Invalid,无法满足需求:
const isValid = <T>(item: Validated<T>): item is Valid<T> => item.isValid; const allValid = <T>(items: Validated<T>[]): items is Valid<T>[] => items.every((item) => item.isValid); const zip = <T>(...items: Validated<T>[]) : Validated<T[]> => items.every((item) => item.isValid) ? { isValid: true, value: items.map((item) => item.value) } : { isValid: false };
如果当前函数形式无法实现,是否有可行的替代方案?
解决方案
要实现保留元组位置类型的zip函数,需要使用可变元组类型和条件类型处理每个参数的Validated类型,同时通过类型守卫对齐运行时逻辑与类型系统。
完整实现代码
type Valid<T> = { isValid: true, value: T }; type Invalid = { isValid: false }; type Validated<T> = Valid<T> | Invalid; const valid = <T>(value: T): Valid<T> => ({ isValid: true, value }); const invalid: Invalid = { isValid: false }; // 类型守卫:判断单个Validated实例是否为Valid类型 const isValid = <T>(item: Validated<T>): item is Valid<T> => item.isValid; // 辅助类型:将Validated元组转换为对应值的元组(仅当所有元素都是Valid时生效) type AllValid<T extends readonly Validated<any>[]> = { [K in keyof T]: T[K] extends Valid<infer V> ? V : never; }; // 主zip函数:使用可变元组泛型保留参数的元组结构 const zip = <T extends readonly Validated<any>[]>(...items: T): T extends { [K in keyof T]: Valid<any> } ? Valid<AllValid<T>> : Invalid => { if (items.every(isValid)) { // 此处需要类型断言,因为TypeScript无法自动推导every后的元组类型窄化 return { isValid: true, value: items.map(item => (item as Valid<any>).value) } as any; } return invalid; };
效果验证
const result1 = zip(valid(1), valid("a"), valid("b"), valid(true)); // result1类型:Valid<[number, string, string, boolean]> | Invalid const result2 = zip(valid(1), invalid, valid(true)); // result2类型:Invalid const result3 = zip(valid("hello"), valid(3.14)); // result3类型:Valid<[string, number]> | Invalid
关键细节说明
- 可变元组泛型:
<T extends readonly Validated<any>[]>让TypeScript将传入参数推断为元组而非普通数组,完整保留每个元素的位置类型信息。 - 条件返回类型:通过
T extends { [K in keyof T]: Valid<any> }检查元组中所有元素是否均为Valid类型,是则返回对应值元组的Valid类型,否则返回Invalid。 - 类型断言必要性:TypeScript的
every方法无法自动将元组类型从Validated<any>[]窄化为Valid<any>[],因此需要as Valid<any>断言,确保map能正确提取value属性。
简化替代方案
若觉得上述实现的类型断言不够简洁,可采用以下写法,返回统一的Validated<AllValid<T>>类型(虽不如前一种精确,但能满足多数场景需求):
const zip = <T extends readonly Validated<any>[]>(...items: T): Validated<AllValid<T>> => { if (items.every(isValid)) { return { isValid: true, value: items.map(item => item.value) as AllValid<T> }; } return invalid; };
内容的提问来源于stack exchange,提问作者Tyler Kasper
相关产品推荐
相关产品推荐

