You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 14:37:13