Skip to content

TypeScript 类型定义:为工具函数编写精确的泛型与返回类型

在构建一个高质量的 TypeScript 工具库时,类型定义是核心竞争力。它不仅是代码的“文档”,更是编译器进行类型检查、智能提示和自动补全的基础。

为每个函数编写精确的泛型与返回类型,能让使用者获得:

  • 编译时错误预防
  • IDE 智能感知(IntelliSense)
  • 类型安全的链式调用
  • 零成本的抽象(Zero-cost abstraction)

以下是针对前述工具函数的工业级类型定义实践

核心原则

  1. 泛型优先:尽可能使用泛型 T, K 等,避免 any
  2. 精确返回类型:使用 Pick<T, K>, Omit<T, K>, ReturnType<F> 等工具类型。
  3. 类型谓词(Type Predicate):用于条件判断后的类型收窄。
  4. 约束泛型:使用 extends 限制泛型范围,防止无效输入。

1. object/omit.ts:移除指定键

目标

  • 输入对象类型 T
  • 要移除的键数组 K extends keyof T
  • 返回类型应自动排除 K 中的键 → Omit<T, K>

实现

ts
// src/object/omit.ts
export function omit<T extends object, K extends keyof T>(
  obj: T,
  keys: K[]
): Omit<T, K> {
  return Object.keys(obj)
    .filter((key) => !keys.includes(key as K))
    .reduce((result, key) => {
      result[key] = obj[key];
      return result;
    }, {} as Omit<T, K>);
}

类型解析

  • T extends object:确保 obj 是对象。
  • K extends keyof T:确保 keys 中的键都存在于 T 上。
  • Omit<T, K>:内置工具类型,生成排除 K 后的新类型。

使用示例

ts
const user = { id: 1, name: 'Alice', password: '123' };
const safeUser = omit(user, ['password']);
// safeUser: { id: number; name: string } ✅ 自动推导

2. function/pipe.ts:函数流水线

目标

实现多函数链的类型安全传递,前一个函数的输出是下一个的输入。

实现(支持最多 5 个函数的重载)

ts
// src/function/pipe.ts

// 单函数
function pipe<A, B>(fn1: (a: A) => B): (a: A) => B;

// 两个函数
function pipe<A, B, C>(
  fn1: (a: A) => B,
  fn2: (b: B) => C
): (a: A) => C;

// 三个函数
function pipe<A, B, C, D>(
  fn1: (a: A) => B,
  fn2: (b: B) => C,
  fn3: (c: C) => D
): (a: A) => D;

// 四个函数
function pipe<A, B, C, D, E>(
  fn1: (a: A) => B,
  fn2: (b: B) => C,
  fn3: (c: C) => D,
  fn4: (d: D) => E
): (a: A) => E;

// 五个函数
function pipe<A, B, C, D, E, F>(
  fn1: (a: A) => B,
  fn2: (b: B) => C,
  fn3: (c: C) => D,
  fn4: (d: D) => E,
  fn5: (e: E) => F
): (a: A) => F;

// 实现
function pipe(...fns: Array<Function>) {
  return <T>(value: T) =>
    fns.reduce((acc, fn) => fn(acc), value);
}

export { pipe };

类型优势

  • 完美类型推导:pipe(add1, mul2, toString)(3) 的返回类型是 string
  • IDE 支持:在 pipe( 后会提示参数类型。

3. type/is.ts:类型谓词(Type Predicate)

目标

if (isString(x)) 分支中,x 的类型被收窄为 string

实现

ts
// src/type/is.ts
const toString = Object.prototype.toString;

// 工厂函数生成类型谓词
const isType = <T>(type: string) => (value: unknown): value is T =>
  toString.call(value) === `[object ${type}]`;

export const isString = isType<string>('String');
export const isNumber = isType<number>('Number');
export const isArray = isType<unknown[]>('Array');
export const isFunction = isType<Function>('Function');
export const isObject = isType<object>('Object');

// 特殊情况:isNil 不改变类型结构,但可区分 null/undefined
export const isNil = (value: unknown): value is null | undefined =>
  value == null;

// isEmpty 不是类型谓词,因为它不指向单一类型
export const isEmpty = (value: unknown): boolean => {
  if (value == null) return true;
  if (isArray(value)) return value.length === 0;
  if (isString(value)) return value === '';
  if (isObject(value)) return Object.keys(value).length === 0;
  return false;
};

关键点

  • value is T 是类型谓词语法。
  • isEmpty 返回 boolean,因为它可能匹配多种空值类型。

4. async/asyncPipe.ts:异步流水线

目标

支持返回 Promise 的函数链,返回 Promise 并保持类型。

实现

ts
// src/async/asyncPipe.ts

// 两个异步函数
function asyncPipe<A, B, C>(
  fn1: (a: A) => Promise<B> | B,
  fn2: (b: B) => Promise<C> | C
): (a: A) => Promise<C>;

// 三个异步函数
function asyncPipe<A, B, C, D>(
  fn1: (a: A) => Promise<B> | B,
  fn2: (b: B) => Promise<C> | C,
  fn3: (c: C) => Promise<D> | D
): (a: A) => Promise<D>;

// 实现
function asyncPipe(...fns: Array<(arg: any) => any>) {
  return async <T>(value: T): Promise<any> => {
    return fns.reduce(async (accPromise, fn) => {
      const acc = await accPromise;
      return fn(acc);
    }, Promise.resolve(value));
  };
}

export { asyncPipe };

类型特性

  • 允许函数同步或异步(| B)。
  • 返回 Promise<C>,调用者需 await
  • 类型逐层传递:A → B → C

5. object/deepGet.ts:深度取值

目标

根据路径字符串安全获取嵌套属性,并支持默认值。

实现

ts
// src/object/deepGet.ts
type Path = string | string[];

// 利用模板字符串类型和递归类型可实现更精确的路径推导(高级)
// 此处为简化版本
export function deepGet<T, D = undefined>(
  obj: T,
  path: Path,
  defaultValue?: D
): Exclude<T[keyof T], undefined> | D {
  if (obj == null) return defaultValue;

  const paths = Array.isArray(path) ? path : path.split('.');
  let current: any = obj;

  for (const key of paths) {
    if (current == null) return defaultValue;
    current = current[key];
  }

  return current !== undefined ? current : defaultValue;
}

类型说明

  • T:输入对象类型。
  • D:默认值类型,可选。
  • 返回类型:原值类型或默认值类型。

进阶:可结合 KeyPath<T> 递归类型实现路径字符串的静态验证。

6. array/chunk.ts:数组分块

实现

ts
// src/array/chunk.ts
export function chunk<T>(array: T[], size: number): T[][] {
  if (size <= 0) return [];
  
  const result: T[][] = [];
  for (let i = 0; i < array.length; i += size) {
    result.push(array.slice(i, i + size));
  }
  return result;
}

类型特点

  • T[] 输入,T[][] 输出。
  • 保持元素类型不变。

总结:类型定义的黄金法则

函数关键类型技术价值
omitOmit<T, K>, K extends keyof T返回类型自动排除指定键
pipe函数重载,类型链传递多函数组合仍保持类型安全
isXXXvalue is T(类型谓词)条件分支内类型自动收窄
asyncPipePromise<T>, 异步类型流异步链式调用不丢失类型
deepGet泛型 T, D,联合返回类型安全访问嵌套属性,支持默认值
chunkT[][]数组操作保持元素类型

最终目标:让使用者在调用 omit(user, ['password']) 时,IDE 能立刻知道返回值没有 password 字段,并阻止 user.password.toUpperCase() 这样的错误调用。

这才是 TypeScript 工具库的终极形态——类型即契约,编译即测试