type-challenges(ts类型体操): 10 - 元组转合集

作者:fxss日期:2026/1/30

10 - 元组转合集

by Anthony Fu (@antfu) #中等 #infer #tuple #union

题目

实现泛型TupleToUnion<T>,它返回元组所有值的合集。

例如

1type Arr = ['1', '2', '3']
2
3type Test = TupleToUnion<Arr> // expected to be '1' | '2' | '3'
4

在 Github 上查看:tsch.js.org/10/zh-CN

代码

1/* _____________ 你的代码 _____________ */
2
3type TupleToUnion<T> = T extends [infer F, ...infer R] ? F | TupleToUnion<R> : never
4
5

关键解释:

  • T extends [infer F, ...infer R] 用于判断元组是否为空。
  • F | TupleToUnion<R> 用于递归处理元组的剩余部分。
  • never 用于处理空元组的情况。

相关知识点

extends

使用维度核心作用示例场景
类型维度做类型约束或条件判断(类型编程核心)限定泛型范围、判断类型是否兼容、提取类型片段
语法维度做继承(复用已有结构)接口继承、类继承
extends 做类型约束或条件判断
  1. 泛型约束:限定泛型的取值范围
1// 约束 T 必须是「拥有 length 属性」的类型(比如 string/数组)
2function getLength<T extends { length: number }>(arg: T): number {
3  return arg.length;
4}
5
6// 合法调用(符合约束)
7getLength("hello"); //  string  length,返回 5
8getLength([1, 2, 3]); //  数组有 length,返回 3
9
10// 非法调用(超出约束)
11getLength(123); //  报错:number 没有 length 属性
12
  1. 条件类型:类型版 三元运算符
1// 基础示例:判断类型是否为字符串
2type IsString<T> = T extends string ? true : false;
3
4type A = IsString<"test">; // true(符合)
5type B = IsString<123>; // false(不符合)
6

分布式条件类型(联合类型专用): 当 T 是联合类型时,extends 会自动拆分联合类型的每个成员,逐个判断后再合并结果。

1type Union = string | number | boolean;
2
3// 拆分逻辑:string→string,number→never,boolean→never  合并为 string
4type OnlyString<T> = T extends string ? T : never;
5type Result = OnlyString<Union>; // Result = string
6

注意:只有泛型参数是 裸类型(没有被 []/{} 包裹)时,才会触发分布式判断:

1// 包裹后不触发分布式,整体判断 [string|number] 是否兼容 [string]
2type NoDist<T> = [T] extends [string] ? T : never;
3type Result2 = NoDist<Union>; // never(整体不兼容)
4
  1. 配合 infer:提取类型片段(黄金组合)
1// 提取 Promise 的返回值类型
2type UnwrapPromise<T> = T extends Promise<infer V> ? V : T;
3
4type C = UnwrapPromise<Promise<string>>; // string(提取成功)
5type D = UnwrapPromise<number>; // number(不满足条件,返回原类型)
6
extends 做继承(复用已有结构)
  1. 接口继承:复用 + 扩展属性
1// 基础接口
2interface User {
3  id: number;
4  name: string;
5}
6
7// 继承 User,并扩展新属性
8interface Admin extends User {
9  role: "admin" | "super_admin"; // 新增权限属性
10}
11
12// 必须包含继承的 + 扩展的所有属性
13const admin: Admin = {
14  id: 1,
15  name: "张三",
16  role: "admin"
17};
18
19// 多接口继承
20interface HasAge { age: number; }
21interface Student extends User, HasAge {
22  className: string; // 同时继承 User + HasAge
23}
24
  1. 类继承:复用父类的属性 / 方法
1class Parent {
2  name: string;
3  constructor(name: string) {
4    this.name = name;
5  }
6  sayHi() {
7    console.log(`Hi, ${this.name}`);
8  }
9}
10
11// 继承 Parent 
12class Child extends Parent {
13  age: number;
14  constructor(name: string, age: number) {
15    super(name); // 必须调用父类构造函数(初始化父类属性)
16    this.age = age;
17  }
18  // 重写父类方法
19  sayHi() {
20    super.sayHi(); // 调用父类原方法
21    console.log(`I'm ${this.age} years old`);
22  }
23}
24
25const child = new Child("李四", 10);
26child.sayHi(); // 输出:Hi, 李四  I'm 10 years old
27

补充:类实现接口用 implements(不是 extends

1// 定义接口(契约:规定必须有 id、name 属性,以及 greet 方法)
2interface Person {
3  id: number;
4  name: string;
5  greet(): void; // 仅定义方法签名,无实现
6}
7
8// 类实现接口(必须严格遵守契约)
9class Employee implements Person {
10  // 必须实现接口的所有属性
11  id: number;
12  name: string;
13
14  // 构造函数初始化属性
15  constructor(id: number, name: string) {
16    this.id = id;
17    this.name = name;
18  }
19
20  // 必须实现接口的 greet 方法(具体实现由类自己定义)
21  greet() {
22    console.log(`Hi, I'm ${this.name}, ID: ${this.id}`);
23  }
24}
25
26// 实例化使用
27const emp = new Employee(1, "张三");
28emp.greet(); // 输出:Hi, I'm 张三, ID: 1
29
30
31// 接口1:基础信息
32interface Identifiable {
33  id: number;
34  getId(): number;
35}
36
37// 接口2:可打印
38interface Printable {
39  printInfo(): void;
40}
41
42// 类同时实现两个接口(必须实现所有接口的成员)
43class Product implements Identifiable, Printable {
44  id: number;
45  name: string; // 类可扩展接口外的属性
46
47  constructor(id: number, name: string) {
48    this.id = id;
49    this.name = name;
50  }
51
52  // 实现 Identifiable 的方法
53  getId(): number {
54    return this.id;
55  }
56
57  // 实现 Printable 的方法
58  printInfo() {
59    console.log(`Product: ${this.name}, ID: ${this.getId()}`);
60  }
61}
62
63const product = new Product(100, "手机");
64console.log(product.getId()); // 100
65product.printInfo(); // Product: 手机, ID: 100
66

infer

infer 是 TypeScript 在条件类型中提供的关键字,用于声明一个 待推导的类型变量(类似给类型起一个临时名字),只能在 extends 子句中使用。它的核心作用是:从已有类型中提取 / 推导我们需要的部分,而无需手动硬编码类型。

infer 必须配合条件类型使用,语法结构如下:

1// 基础结构:推导 T 的类型为 U,若能推导则返回 U,否则返回 never
2type InferType<T> = T extends infer U ? U : never;
3
4type Example = InferType<string>; // Example 类型为 string
5type Example2 = InferType<number[]>; // Example2 类型为 number[]
6

高频使用场景:

1. 提取函数的返回值类型
1// 定义类型工具:提取函数的返回值类型
2type GetReturnType<Fn> = Fn extends (...args: any[]) => infer R ? R : never;
3
4// 测试用函数
5const add = (a: number, b: number): number => a + b;
6const getUser = () => ({ name: "张三", age: 20 });
7
8// 使用类型工具
9type AddReturn = GetReturnType<typeof add>; // AddReturn 类型为 number
10type UserReturn = GetReturnType<typeof getUser>; // UserReturn 类型为 { name: string; age: number }
11
2. 提取数组的元素类型
1// 定义类型工具:提取数组元素类型
2type GetArrayItem<T> = T extends (infer Item)[] ? Item : never;
3
4// 测试
5type NumberArray = GetArrayItem<number[]>; // NumberArray 类型为 number
6type StringArray = GetArrayItem<string[]>; // StringArray 类型为 string
7type MixedArray = GetArrayItem<[string, number]>; // MixedArray 类型为 string | number
8
3. 提取 Promise 的泛型参数类型
1// 定义类型工具:提取 Promise 的泛型类型
2type GetPromiseValue<T> = T extends Promise<infer Value> ? Value : never;
3
4// 测试
5type PromiseString = GetPromiseValue<Promise<string>>; // PromiseString 类型为 string
6type PromiseUser = GetPromiseValue<Promise<{ id: number }>>; // PromiseUser 类型为 { id: number }
7
4. 提取函数的参数类型
1// 定义类型工具:提取函数参数类型
2type GetFunctionParams<Fn> = Fn extends (...args: infer Params) => any ? Params : never;
3
4// 测试
5const fn = (name: string, age: number): void => {};
6type FnParams = GetFunctionParams<typeof fn>; // FnParams 类型为 [string, number]
7
8// 进一步:提取第一个参数的类型
9type FirstParam = GetFunctionParams<typeof fn>[0]; // FirstParam 类型为 string
10

|

| 运算符用于表示联合类型,即一个值可以是多个类型中的任意一个。

  1. 变量的联合类型注解
1// 变量 a 可以是字符串 OR 数字
2let a: string | number;
3
4// 合法赋值(符合任意一种类型)
5a = "TS";
6a = 123;
7
8// 非法赋值(不属于联合类型中的任何一种),TS 直接报错
9a = true; //  类型 'boolean' 不能赋值给类型 'string | number'
10
  1. 函数参数的联合类型
1// 函数接收 string  number 类型的参数
2function printValue(val: string | number) {
3  console.log(val);
4}
5
6// 合法调用
7printValue("hello");
8printValue(666);
9
10// 非法调用,TS 报错
11printValue(null); // 
12
  1. 数组的联合类型(注意两种写法的区别)
1// 写法1:(A | B)[] —— 数组的「每个元素」可以是 A  B(混合数组)
2let arr1: (string | number)[] = [1, "2", 3, "4"]; // 合法
3
4// 写法2:A[] | B[] —— 「整个数组」要么全是 A 类型,要么全是 B 类型(纯数组)
5let arr2: string[] | number[] = [1, 2, 3]; // 合法(全数字)
6arr2 = ["1", "2", "3"]; // 合法(全字符串)
7arr2 = [1, "2"]; //  报错:混合类型不符合要求
8

当使用联合类型的时候,访问某一个子类型的专属属性 / 方法时,需要进行类型守卫,可用的方法有 typeofinswitchinstanceof

  1. typeof
1function getLength(val: string | number) {
2  // 类型窄化:判断 val  string 类型
3  if (typeof val === "string") {
4    // 此分支中,TS 确定 val  string,可安全使用 length
5    return val.length;
6  } else {
7    // 此分支中,TS 确定 val  number,执行数字相关逻辑
8    return val.toString().length;
9  }
10}
11
12console.log(getLength("TS")); // 2
13console.log(getLength(1234)); // 4
14
  1. in
1function printUserInfo(user: { name: string } | { age: number }) {
2  // 类型窄化:判断 user 是否有 name 属性(即是否是 { name: string } 类型)
3  if ("name" in user) {
4    console.log(`Name: ${user.name}`);
5  } else {
6    // 此分支中,TS 确定 user  { age: number } 类型
7    console.log(`Age: ${user.age}`);
8  }
9}
10
  1. switch
1interface User {
2  type: "user";
3  name: string;
4  age: number;
5}
6interface Admin {
7  type: "admin";
8  name: string;
9  permission: string[];
10}
11// 联合类型:可以是 User  Admin
12type Person = User | Admin;
13function printPerson(p: Person) {
14  switch (p.type) {
15    case "user":
16      console.log(p.age); // 确定是 User
17      break;
18    case "admin":
19      console.log(p.permission); // 确定是 Admin
20      break;
21  }
22}
23
  1. instanceof
1// 定义两个类
2class Dog {
3  bark() { console.log("汪汪"); }
4}
5class Cat {
6  meow() { console.log("喵喵"); }
7}
8
9// 联合类型:Dog  Cat 实例
10type Animal = Dog | Cat;
11
12// instanceof 类型守卫(针对类实例)
13function animalCall(animal: Animal) {
14  if (animal instanceof Dog) {
15    animal.bark();
16  } else {
17    animal.meow();
18  }
19}
20
21animalCall(new Dog()); // 汪汪
22animalCall(new Cat()); // 喵喵
23

测试用例

1/* _____________ 测试用例 _____________ */
2import type { Equal, Expect } from '@type-challenges/utils'
3
4type cases = [
5  Expect<Equal<TupleToUnion<[123, '456', true]>, 123 | '456' | true>>,
6  Expect<Equal<TupleToUnion<[123]>, 123>>,
7]
8
9

相关链接

分享你的解答:tsch.js.org/10/answer/z…查看解答:tsch.js.org/10/solution…更多题目:tsch.js.org/zh-CN

下面是我的公众号,欢迎关注。关注后有新的功能点会及时收到推送。

实战为王!专注于汇总各种功能点,致力于打造一系列能够帮助工程师实现各种功能的想法思路的优质文章。

前端功能点


type-challenges(ts类型体操): 10 - 元组转合集》 是转载文章,点击查看原文


相关推荐


LeetCode 377 组合总和 Ⅳ
展菲2026/1/21

文章目录 摘要描述题解答案题解代码分析1. 动态规划的基本思路2. 初始状态3. 状态转移方程4. 为什么这样能计算出排列个数?5. 与组合问题的区别6. 优化:避免不必要的计算 示例测试及结果示例 1:nums = [1,2,3], target = 4示例 2:nums = [9], target = 3示例 3:nums = [1,2], target = 3示例 4:nums = [1], target = 1 时间复杂度空间复杂度进阶问题:如果数组中含有负数问题分析解决方


HarmonyOS一杯冰美式的时间 -- UIUtils基础功能
猫猫头啊2026/1/13

一、前言 最近在写状态管理相关的代码,发现 HarmonyOS 的 UIUtils 这个工具类还挺实用的。它主要解决一些状态管理框架在使用过程中遇到的边界问题,比如代理对象、V1/V2 混用、数据绑定这些场景。 今天顺手整理一下它的几个核心功能,方便以后查。 该系列依旧会带着大家,了解,开阔一些不怎么热门的API,也可能是偷偷被更新的API,也可以是好玩的,藏在官方文档的边边角角~当然也会有一些API,之前是我们辛辛苦苦的手撸代码,现在有一个API能帮我们快速实现的,希望大家能找宝藏。 如果您有


Elasticsearch 8.13.4 动态同义词实战全解析
detayun2026/1/4

在搜索引擎的江湖里,“词不达意"往往是阻碍用户找到心仪内容的最后一道鸿沟。当用户搜索"番茄"时,如果你的库里只有"西红柿"和"圣女果”,传统的精确匹配只能让用户空手而归。同义词库,便是那把填补语义裂痕的钥匙。然而,在 Elasticsearch 8.13.4 这个版本中,我们不再满足于重启服务来更新词库的"笨办法",我们要的是如丝般顺滑的动态热更新。 今天,我们就来一场技术突围,深度剖析在 ES 8.13.4 时代,如何玩转动态同义词,让你的搜索引擎拥有"自我进化"的灵魂。 一、 告别"文件搬运


卷积神经网络CNN
代码洲学长2025/12/26

CNN简介 卷积神经网络就是一个包括卷积层和池化层的神经网络,主要应用于计算机视觉方面,应用场景包括图像分类、目标检测、面部解锁、自动驾驶等。 整体架构流程 CNN的主要结构为 输入层,隐藏层 和输出层,主体架构主要体现在隐藏层中的网络,依次为卷积层 池化层 然后全连接层直接输出。CNN分别进行了两场卷积和池化 ,最终通过三个全连接层进行输出。 卷积层结构图 input(32, 32, 3) conv(3, 3, 6) relu(30, 30, 6) pool(2, 2, 6)


设计模式——责任链模式实战,优雅处理Kafka消息
KD2025/12/18

一、业务背景 Kafka接收消息,需要A,B,C...多种策略做处理,再通过http请求发送给下游。多种策略混在一起很难维护,通过责任链模式把每种策略的代码收敛到自己的Handler中 二、具体设计 classDiagram Handler <|-- StrategyAHandler Handler <|-- StrategyBHandler Handler <|-- UploadHandler Handler: +void handleUserData(UserContext, Handler


【Perfetto从入门到精通】2. 使用 Perfetto 追踪/分析 APP 的 Native/Java 内存
Lei_official2025/12/10

这个世界就是这样,你从失败中学到的东西可能比成功中学到的东西更多——《Android 传奇》 说起 Android APP 内存分析,我们第一时间想到的,往往是 Android Studio Profiler、MAT 这样的老牌工具,而 Perfetto 的出现,又为其提供了一种更加贴近底层的视角。而且相比于现有的工具,Perfetto 更加擅长于分析 Native 内存占用,可以说是补齐了工程师在这方面的短板。 在内存方向,我计划用2~3篇文章来介绍 Perfetto 的功能、特点、使用方


前端高频面试题之CSS篇(二)
程序员小寒2025/11/30

1、如何实现两栏布局? 两栏布局指的是左边宽度固定,右边宽度自适应。 DOM 结构如下: <body> <div class="box"> <div class="left"></div> <div class="right"></div> </div> </body> 1.1 利用 flex 布局实现 实现思路:将父元素设为 flex 布局,左边元素宽度固定,右边元素设为 flex: 1,即自适应。 .box { display: flex; width: 5


用 Python 30 分钟做出自己的记事本
忘忧记2026/2/8

🌟 《零基础手把手:用 Python 30 分钟做出自己的记事本》 —— 不是照抄代码,而是理解每行代码的「灵魂」 🧩 第一步:为什么我们需要「基础窗口」?(新手必懂!) ❌ 常见错误:直接写 window.show() 但窗口不显示? ✅ 正确逻辑:程序运行流程图 #mermaid-svg-OKGSEAHaVwdJVXr3{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyf


丰田正在使用 Flutter 开发游戏引擎 Fluorite
恋猫de小郭2026/2/17

近日,丰田汽车旗下子公司丰田互联北美公司宣布,即将开源基于 Flutter 的自主研发的游戏引擎 Flourite ,而实际上在此之前,Flutter 已经是丰田车机的开发 SDK 之一。 Toyota Connected North America,TCNA,是丰田的北美子公司,专注于车载软件、AI 等。 Fluorite 是首款完全集成 Flutter 的主机级(console-grade)游戏引擎,主要针对车载数字座舱(digital cockpit)和嵌入式低端硬件设计,已在 202


【深度学习基础篇04】从回归到分类:图像分类与卷积神经网络入门
ppppppatrick2026/2/25

【深度学习基础篇】从回归到分类:图像分类与卷积神经网络入门 文章目录 【深度学习基础篇】从回归到分类:图像分类与卷积神经网络入门一、前情提要:从回归实战到分类任务的核心转变回归与分类的核心区别:输出逻辑的本质不同分类任务的输出解码:从“置信度”到“类别标签” 二、图像分类的前提:理解图像的张量表示1. 图像的核心维度:通道×高度×宽度(C×H×W)2. 批量图像的张量格式:N×C×H×W3. 全连接层处理图像的痛点:维度爆炸 三、卷积的核心概念:从“局部感知”到特征提取1

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 XYZ博客