TypeScript 类型定义:为工具函数编写精确的泛型与返回类型
在构建一个高质量的 TypeScript 工具库时,类型定义是核心竞争力。它不仅是代码的“文档”,更是编译器进行类型检查、智能提示和自动补全的基础。
为每个函数编写精确的泛型与返回类型,能让使用者获得:
- 编译时错误预防
- IDE 智能感知(IntelliSense)
- 类型安全的链式调用
- 零成本的抽象(Zero-cost abstraction)
以下是针对前述工具函数的工业级类型定义实践。
核心原则
- 泛型优先:尽可能使用泛型
T,K等,避免any。 - 精确返回类型:使用
Pick<T, K>,Omit<T, K>,ReturnType<F>等工具类型。 - 类型谓词(Type Predicate):用于条件判断后的类型收窄。
- 约束泛型:使用
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[][]输出。- 保持元素类型不变。
总结:类型定义的黄金法则
| 函数 | 关键类型技术 | 价值 |
|---|---|---|
omit | Omit<T, K>, K extends keyof T | 返回类型自动排除指定键 |
pipe | 函数重载,类型链传递 | 多函数组合仍保持类型安全 |
isXXX | value is T(类型谓词) | 条件分支内类型自动收窄 |
asyncPipe | Promise<T>, 异步类型流 | 异步链式调用不丢失类型 |
deepGet | 泛型 T, D,联合返回类型 | 安全访问嵌套属性,支持默认值 |
chunk | T[][] | 数组操作保持元素类型 |
最终目标:让使用者在调用 omit(user, ['password']) 时,IDE 能立刻知道返回值没有 password 字段,并阻止 user.password.toUpperCase() 这样的错误调用。
这才是 TypeScript 工具库的终极形态——类型即契约,编译即测试。