如何为同类型嵌套配对参数函数添加TS类型?TypeScript有无类型类等价特性?
解决方案:为递归配对类型的
plus函数添加类型定义 一、正确的plus函数类型约束
原方案的问题在于允许T为联合类型(如number | [number, number]),导致非法调用无法被拦截。我们可以通过非联合类型约束+递归类型断言解决这个问题:
// 定义递归的嵌套数值配对类型 type NestedNumberPair = number | [NestedNumberPair, NestedNumberPair]; // 辅助类型:判断类型是否为联合类型 type IsUnion<T, U = T> = T extends U ? [U] extends [T] ? false : true : false; // 辅助类型:仅允许非联合类型,拦截联合类型参数 type NotUnion<T> = IsUnion<T> extends true ? never : T; function plus<T extends NestedNumberPair>(a: NotUnion<T>, b: NotUnion<T>): T { if (Array.isArray(a)) { // 递归调用后断言返回类型为T(编译器无法自动递归推断) return [plus(a[0], b[0]), plus(a[1], b[1])] as T; } else { // 断言数值相加结果为T return (a + b) as T; } } // 合法调用(类型检查通过) plus(1, 2); // 返回类型:number plus([1, 2], [3, 4]); // 返回类型:[number, number] plus([1, [2, 3]], [10, [10, 10]]); // 返回类型:[number, [number, number]] // 非法调用(类型检查报错) plus(1, [10, 10]); // 参数类型不匹配 plus([0, [1, 1]], [[0, 0], 1]); // 参数结构不匹配
核心逻辑
NestedNumberPair严格定义了允许的递归结构:要么是number,要么是两个同结构元素组成的数组。NotUnion<T>通过类型判断拦截联合类型,确保参数必须是单一的嵌套结构,而非多种结构的混合。- 递归调用后的类型断言是必要的:TypeScript编译器无法自动推断递归返回值与
T的匹配关系,需要手动断言确认。
二、TypeScript中模拟类型类(Type Class)的特性
TypeScript没有原生的类型类支持,但可以通过接口+泛型+递归条件类型模拟类型类的核心能力——即ad-hoc多态(根据类型自动选择对应实现)。
方式1:基于接口的类型类模拟
定义通用的可相加接口,为不同类型实现该接口:
// 定义"可相加"类型类接口 interface Addable<T> { add(a: T, b: T): T; } // 为number类型实现Addable const NumberAddable: Addable<number> = { add(a, b) { return a + b; } }; // 递归为嵌套配对类型实现Addable type NestedPair<T> = T | [NestedPair<T>, NestedPair<T>]; function getAddable<T extends number>(): Addable<NestedPair<T>> { return { add(a, b) { if (Array.isArray(a)) { const pairAddable = getAddable<T>(); return [pairAddable.add(a[0], b[0]), pairAddable.add(a[1], b[1])] as NestedPair<T>; } else { return NumberAddable.add(a, b) as NestedPair<T>; } } }; } // 获取类型安全的plus函数 const plus = getAddable<number>().add;
方式2:基于条件类型的分发
利用TypeScript的条件类型分发特性,直接在函数类型中处理不同层级的嵌套结构:
type NestedNumberPair = number | [NestedNumberPair, NestedNumberPair]; // 递归计算相加后的返回类型 type AddResult<T extends NestedNumberPair> = T extends number ? number : T extends [infer A, infer B] ? [AddResult<A>, AddResult<B>] : never; function plus<T extends NestedNumberPair>(a: T, b: T): AddResult<T> { if (Array.isArray(a)) { return [plus(a[0], b[0]), plus(a[1], b[1])] as AddResult<T>; } else { return (a + b) as AddResult<T>; } }
说明
这两种方式都实现了类似类型类的效果:针对不同类型(基础类型、嵌套结构)提供对应的相加逻辑,同时保证类型安全。但相比Haskell等原生支持类型类的语言,TypeScript的模拟需要更多手动编写的代码,无法自动推导实例。
内容的提问来源于stack exchange,提问作者Feuermurmel
相关产品推荐
相关产品推荐

