mcpField
V
variadic-tuple-types

variadic-tuple-types

github.com/marius-townhouse开发643 次安装38 周前更新
代码仓库

关于 variadic-tuple-types

面向 TypeScript 开发者的技能,讲解如何为可变参数函数添加精确类型,并用剩余参数、元组和泛型保留参数类型,涵盖组合与柯里化模式。

主要功能

  1. 01精确标注可变参数函数
  2. 02用元组保留参数类型
  3. 03用泛型处理类型转换
  4. 04将元组用于组合与柯里化

适用场景

  1. 01为可变参数函数添加类型
  2. 02构建类型安全的函数组合工具
  3. 03实现类型安全的柯里化
  4. 04用元组建模事件参数

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/marius-townhouse/effective-typescript-skills --skill variadic-tuple-types

安装后在 Claude Code 中输入/variadic-tuple-types确认已加载。

详细介绍

使用剩余参数和元组类型为可变参数函数建模

概述

可变参数函数是指接受数量不定的参数的函数,在 JavaScript 中很常见。TypeScript 的元组类型和剩余参数可以让你精确地为这类函数添加类型,同时保留参数的数量和类型。这使得类型化函数组合和泛型管道等强大模式成为可能。

了解如何结合使用剩余参数和元组类型,可以为灵活的 API 实现精确的类型定义。

何时使用此技能

  • 函数接受数量不定且带有类型的参数
  • 需要在转换过程中保留元组长度
  • 构建泛型函数组合工具
  • 为带有特定约束的剩余参数添加类型
  • 创建类型化函数管道

铁律

使用元组类型为剩余参数添加类型,从而精确描述可变参数函数。结合泛型,在转换过程中保留参数类型。

检测

留意以下未添加类型的模式:

// RED FLAGS - Untyped variadic functions
function logAll(...args: any[]): void;  // Lost type information
function compose(...fns: Function[]): Function;  // No type safety
function curry(fn: Function): Function;  // Arguments not tracked

使用元组的基本剩余参数

// Preserve exact argument types
function logAll<T extends any[]>(...args: T): void {
  console.log(args);
}

logAll(1, 'hello', true);
// T is inferred as [number, string, boolean]

// Constrain the tuple
type StringNumberPair = [string, number];
function formatPair(...pair: StringNumberPair): string {
  return `${pair[0]}: ${pair[1]}`;
}

formatPair('score', 100);  // OK
formatPair('score');       // Error: missing number
formatPair('score', 100, 'extra');  // Error: too many arguments

泛型可变参数函数

// Preserve types through transformation
function tail<T extends any[]>(
  head: T[0],
  ...rest: T extends [any, ...infer R] ? R : never
): T {
  return [head, ...rest] as T;
}

const result = tail(1, 'a', true);
// result: [number, string, boolean]

函数组合

// Compose functions with preserved types
type Fn = (...args: any[]) => any;

function compose<T extends Fn[]>(
  ...fns: T
): (...args: Parameters<T[0]>) => ReturnType<T[number]> {
  return (arg) => fns.reduceRight((acc, fn) => fn(acc), arg);
}

const add1 = (x: number) => x + 1;
const double = (x: number) => x * 2;
const toString = (x: number) => String(x);

const composed = compose(toString, double, add1);
// (x: number) => string

const result = composed(5);  // "12"

使用元组类型实现柯里化

// Type-safe currying
type Curry<T extends any[], R> = T extends [infer First, ...infer Rest]
  ? (arg: First) => Rest extends [] ? R : Curry<Rest, R>
  : () => R;

function curry<T extends any[], R>(
  fn: (...args: T) => R
): Curry<T, R> {
  return ((arg: any) => {
    if (arguments.length >= fn.length) {
      return fn(...arguments);
    }
    return curry(fn.bind(null, arg));
  }) as Curry<T, R>;
}

const sum3 = (a: number, b: number, c: number) => a + b + c;
const curried = curry(sum3);

const result = curried(1)(2)(3);  // 6
// Each step is correctly typed!

实际示例:事件发射器

type EventMap = {
  click: [x: number, y: number];
  keypress: [key: string];
  load: [];
};

class TypedEmitter<Events extends Record<string, any[]>> {
  emit<K extends keyof Events>(
    event: K,
    ...args: Events[K]
  ): void {
    // Implementation
  }
  
  on<K extends keyof Events>(
    event: K,
    handler: (...args: Events[K]) => void
  ): void {
    // Implementation
  }
}

const emitter = new TypedEmitter<EventMap>();

emitter.emit('click', 100, 200);  // OK
emitter.emit('click', 100);       // Error: missing y
emitter.on('keypress', (key) => {
  console.log(key);  // key is string
});

抗压协议

为可变参数函数添加类型时:

  1. 使用元组约束:使用 [string, number],而不是 string | number
  2. 通过泛型保留类型:T extends any[] 可以捕获确切类型
  3. 考虑长度:使用元组长度定义条件类型
  4. 测试边界情况:空元组、单个元素、多个元素

危险信号

反模式问题解决方案
...args: any[]丢失类型信息T extends any[]
Function 类型没有参数和返回值类型泛型函数类型
使用数组而不是元组丢失长度信息元组类型

常见借口

“这对我的使用场景来说太复杂了”

实际情况:从 T extends any[] 开始,仅在需要时增加复杂度。

“用户可以把参数作为数组传递”

实际情况:剩余参数使用起来更顺手。正确地为其添加类型,才能获得最佳开发体验。

“我直接使用重载就行了”

实际情况:元组可以处理通用情况。重载则需要手动枚举。

快速参考

模式语法使用场景
捕获参数T extends any[]保留参数类型
限制长度[A, B, C]固定数量的参数
最小长度[A, B, ...C[]]至少 2 个参数
转换Parameters<T[0]>提取参数类型

总结

使用元组类型的剩余参数可以精确地为可变参数函数添加类型。使用泛型在转换过程中保留参数类型,从而实现类型化组合等强大模式。

参考资料

  • Dan Vanderkam 著《Effective TypeScript》第 2 版
  • 条目 62:使用剩余参数和元组类型为可变参数函数建模

评论

支持 Markdown