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

如何为TypeScript中链式递归的可组合对象实现完整类型定义?

解决TypeScript链式可组合对象生成函数的类型累积问题

你遇到的核心问题是如何在链式调用的compose函数中保留每一步组合的类型信息,避免之前实现中any导致的类型丢失。其实我们可以利用TypeScript的递归类型特性来实现这个需求,不需要复杂的Fix类型,直接定义一个递归的组合类型即可。

解决方案:递归类型+类型累积

首先,我们定义一个递归的Composed<T>类型,它既包含当前组合对象的属性T,又包含一个then方法——这个方法会接受一个返回新对象的thunk,并返回一个合并了原有类型和新类型的Composed实例:

// 定义递归的组合类型:包含当前对象属性 + 链式then方法
type Composed<T> = T & {
  then<U extends {}>(thunk: () => U): Composed<T & U>;
};

接下来实现compose函数,核心是在then方法中递归调用compose,并通过类型断言确保返回的类型是合并后的Composed<T & U>:

const compose = <T extends {}>(thunk: () => T): Composed<T> => {
  const currentObj = { ...thunk() };
  
  return {
    ...currentObj,
    then: <U extends {}>(nextThunk: () => U) => 
      compose(() => ({ ...currentObj, ...nextThunk() })) as Composed<T & U>
  };
};

测试效果

现在我们来验证类型是否正确累积:

// 第一次组合:{ foo: number }
const step1 = compose(() => ({ foo: 1 }));
// 第二次组合:合并{ bar: number },类型变为{ foo: number } & { bar: number }
const combined = step1.then(() => ({ bar: 2 }));

// 访问属性时会有正确的类型提示
console.log(combined.foo); // 类型:number
console.log(combined.bar); // 类型:number

// 继续链式调用,类型会继续累积
const combinedWithBaz = combined.then(() => ({ baz: "hello" }));
console.log(combinedWithBaz.baz); // 类型:string

此时combined的类型完全符合你的预期:{ foo: number } & { bar: number } & { then: <U extends {}>(thunk: () => U) => Composed<...> },所有属性的类型都被正确保留。

为什么原来的实现会丢失类型?

你之前的compose2使用了Compose<any>作为then方法的返回类型,这直接切断了类型的累积链条——每次调用then都会回到any类型,自然无法保留之前的属性类型。而我们的递归Composed<T>类型会在每次then调用时,将新的对象类型U与原有类型T做交叉合并,从而持续累积类型信息。

额外说明

  • 如果多个组合对象有同名属性,交叉类型会遵循TypeScript的规则:后面的属性类型会覆盖前面的,这和对象扩展的运行时行为一致。
  • 我们用<T extends {}>约束了thunk的返回值必须是对象类型,避免传入基本类型导致的类型异常。

内容的提问来源于stack exchange,提问作者Aleksandar Dimitrov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 15:42:37