You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现支持依赖前置方法的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类型,没有考虑方法数组的顺序依赖关系——后续方法应该能访问前面所有方法和基础对象的合并类型。我们需要通过可变元组的递归类型推导来实现类型的累加:

  1. 定义递归类型,依次推导每个方法对象的this上下文:它应该是基础对象类型加上前面所有方法对象的类型合并。
  2. 调整mix函数的泛型参数,让它能接收并推导有序的方法元组类型,同时确保每个方法的this指向当前累加后的类型。
  3. 修正函数返回值的类型,使其包含基础对象和所有方法的完整类型。

下面是修正后的完整代码:

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.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 23:47:39