如何为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
相关产品推荐
相关产品推荐

