如何实现支持依赖前置方法的TypeScript mixin函数类型定义
问题描述
我实现了一个mix函数,用于创建包含基础数据和多组方法的"mixin"对象——它接收一个包含数据的基础对象,以及一个包含方法的对象数组,通过在新对象的原型上挂载所有方法、再复制基础数据来生成目标对象:
type ThisFn<T> = (this: T, ...args: any[]) => any; function mix<B extends {}>(base: B, methods: Record<string, ThisFn<B>>[]) { const self = Object.create(methods.reduce(Object.assign)); for (const [k, v] of Object.entries(base)) self[k] = v; return self; }
当前这个函数在挂载仅依赖基础对象的方法时运行正常,但我希望后续的方法能依赖之前已经挂载的方法。比如下面的场景中,fullnamed对象里的fullname方法依赖前面named和surnamed的方法,但类型校验报错:
const named = { name(this: { _name?: string }) { return this._name; }, setName(this: { _name?: string }, name: string) { this._name = name; return this; }, }; const surnamed = { surname(this: { _surname?: string }) { return this._surname; }, setSurname(this: { _surname?: string }, surname: string) { this._surname = surname; return this; }, }; const fullnamed = { fullname(this: { name(): string | undefined; surname(): string | undefined; }) { return this.name()! + ` ` + this.surname()!; }, }; const foo = mix({ _name: undefined, _surname: undefined }, [ named, // 类型校验通过 surnamed, // 类型校验通过 fullnamed, // 报错:缺少属性 '{ name(): string | undefined; surname(): string |undefined; }' ]);
我尝试用递归累加器来定义方法数组的类型,但没成功:
type MethodChain< Base extends {}, T extends unknown[], Acc extends {} = Base > = T extends [infer First, ...infer Rest] ? First extends Record<string, ThisFn<Acc>> ? MethodChain<Base, Rest, Acc & First> : never : T;
请问有什么可行的解决思路?
解决思路
核心问题在于原函数的类型定义强制所有方法都仅依赖初始的Base类型,没有考虑方法数组的顺序依赖关系——后续方法应该能访问前面所有方法和基础对象的合并类型。我们需要通过可变元组的递归类型推导来实现类型的累加:
- 定义递归类型,依次推导每个方法对象的
this上下文:它应该是基础对象类型加上前面所有方法对象的类型合并。 - 调整
mix函数的泛型参数,让它能接收并推导有序的方法元组类型,同时确保每个方法的this指向当前累加后的类型。 - 修正函数返回值的类型,使其包含基础对象和所有方法的完整类型。
下面是修正后的完整代码:
type ThisFn<T> = (this: T, ...args: any[]) => any; // 递归推导方法链的累加类型,同时校验每个方法的this上下文 type ValidMethodChain<Base extends {}, Methods extends unknown[], Acc extends {} = Base> = Methods extends [infer First, ...infer Rest] ? First extends Record<string, ThisFn<Acc>> ? ValidMethodChain<Base, Rest, Acc & First> : never : Acc; // 提取方法数组的元素类型,要求每个元素的方法this指向当前累加类型 type MethodArray<Base extends {}, Acc extends {} = Base> = Array<Record<string, ThisFn<Acc>>> | [Record<string, ThisFn<Acc>>, ...MethodArray<Base, Acc & Record<string, ThisFn<Acc>>>]; function mix<Base extends {}, Methods extends MethodArray<Base>>( base: Base, methods: Methods ): ValidMethodChain<Base, Methods> { const proto = methods.reduce((acc, curr) => Object.assign(acc, curr), {}); const self = Object.create(proto); Object.assign(self, base); return self as ValidMethodChain<Base, Methods>; } // 测试代码 const named = { name(this: { _name?: string }) { return this._name; }, setName(this: { _name?: string }, name: string) { this._name = name; return this; }, }; const surnamed = { surname(this: { _surname?: string }) { return this._surname; }, setSurname(this: { _surname?: string }, surname: string) { this._surname = surname; return this; }, }; const fullnamed = { fullname(this: { name(): string | undefined; surname(): string | undefined; }) { return this.name()! + ` ` + this.surname()!; }, }; // 现在类型校验完全通过,foo可以正确访问所有方法 const foo = mix({ _name: undefined, _surname: undefined }, [named, surnamed, fullnamed]); foo.setName("John"); foo.setSurname("Doe"); console.log(foo.fullname()); // "John Doe"
关键说明
ValidMethodChain:递归遍历方法元组,每次将当前方法对象的类型合并到累加器Acc中,同时校验当前方法的this是否符合累加后的类型。MethodArray:定义方法数组的结构,确保后续方法的this上下文是前面所有类型的合并,实现顺序依赖的类型校验。- 函数返回值通过
ValidMethodChain推导,确保返回的对象包含基础数据和所有方法的完整类型,支持类型提示和校验。
内容的提问来源于stack exchange,提问作者Adam B.
相关产品推荐
相关产品推荐

