如何在TypeScript父子构建器模式中正确为转换器添加类型?
父子构建器模式的TypeScript类型传递问题
我在TypeScript中实现父子构建器模式,核心逻辑如下:
- ParentBuilder负责创建ChildBuilder
- ParentBuilder的
mod方法用于存储转换函数 - ParentBuilder的
child方法接收具体值,生成对应的ChildBuilder - ChildBuilder默认继承ParentBuilder的转换函数,同时支持用自身的
mod方法重写
目前遇到的问题是:ParentBuilder的转换器类型无法正确将类型关联传递给ChildBuilder,导致先调用mod再创建ChildBuilder时,最终返回类型不符合预期。
问题代码示例
type ParentBuilder<M = any> = { mod: <M2>(transformer: (val: any) => M2) => ParentBuilder<M2>; child: <V>(value: V) => ChildBuilder<V, M>; }; type ParentBuilderDef = { transformer: (val: any) => any; }; const parentBuilder = (def: ParentBuilderDef): ParentBuilder => ({ mod: (transformer) => parentBuilder({ ...def, transformer, }), child: (value) => childBuilder({ transformer: def.transformer, value, }), }); type ChildBuilder<V, M> = { build: () => M; mod: <M2>(transformer: (val: V) => M2) => ChildBuilder<V, M2>; }; type ChildBuilderDef<V, M> = { value: V; transformer: (val: V) => M; }; const childBuilder = <V, M>(def: ChildBuilderDef<V, M>): ChildBuilder<V, M> => ({ mod: (transformer) => childBuilder({ ...def, transformer, }), build: () => def.transformer(def.value), }); const myBuilder = parentBuilder({ transformer: (val) => val, }); function xform<T>(x: T) { return { output: x }; } const configuredBuilder1 = myBuilder .child('hello' as const) .mod(xform) .build(); // ✅ 预期&实际:{ output: 'hello'; } const configuredBuilder2 = myBuilder .mod(xform) .child('hello' as const) .build(); // ❌ 预期:{ output: 'hello'; } 实际:{ output: any }
问题核心
ChildBuilder的转换器类型能正常推导,是因为此时输入值V的类型已知;但ParentBuilder的泛型M仅表示固定的输出类型,无法关联“输入V→输出M”的动态关系(因为Child的输入值V还未确定)。要解决这个问题,需要用**高阶类型(HKT)**描述这种“输入类型到输出类型的映射”,而非直接固定输出类型。
方案一:纯TypeScript实现(手动模拟HKT)
无需第三方库,通过定义泛型转换函数类型,让ParentBuilder存储“类型映射关系”而非固定输出类型。
修改后代码
// 定义转换函数类型:接收输入V,返回输出M type Transformer<V, M> = (val: V) => M; // ParentBuilder的泛型F表示"输入V→输出F<V>"的类型映射 type ParentBuilder<F extends (v: any) => any = <V>(v: V) => V> = { mod: <F2 extends (v: any) => any>(transformer: F2) => ParentBuilder<F2>; child: <V>(value: V) => ChildBuilder<V, F<V>>; }; type ParentBuilderDef<F extends (v: any) => any> = { transformer: F; }; const parentBuilder = <F extends (v: any) => any>(def: ParentBuilderDef<F>): ParentBuilder<F> => ({ mod: (transformer) => parentBuilder({ ...def, transformer, }), child: (value) => childBuilder({ transformer: def.transformer, value, }), }); // ChildBuilder部分保持不变 type ChildBuilder<V, M> = { build: () => M; mod: <M2>(transformer: (val: V) => M2) => ChildBuilder<V, M2>; }; type ChildBuilderDef<V, M> = { value: V; transformer: (val: V) => M; }; const childBuilder = <V, M>(def: ChildBuilderDef<V, M>): ChildBuilder<V, M> => ({ mod: (transformer) => childBuilder({ ...def, transformer, }), build: () => def.transformer(def.value), }); const myBuilder = parentBuilder({ transformer: <V>(val: V) => val, }); function xform<T>(x: T) { return { output: x }; } const configuredBuilder1 = myBuilder .child('hello' as const) .mod(xform) .build(); // ✅ 类型:{ output: 'hello' } const configuredBuilder2 = myBuilder .mod(xform) .child('hello' as const) .build(); // ✅ 类型:{ output: 'hello' }
关键改动
- ParentBuilder的泛型从固定输出类型
M改为类型映射函数F,F<V>表示输入V时的输出类型 mod方法接收完整的泛型转换器,而非仅输出类型child方法创建ChildBuilder时,通过F<V>自动推导输出类型,实现输入值类型与转换结果的关联
方案二:使用Effect-TS的HKT实现
Effect-TS提供了成熟的高阶类型工具链,能更规范地处理复杂类型映射关系。
步骤1:安装依赖
npm install @effect/data
修改后代码
import { Kind, TypeLambda } from "@effect/data/HKT"; // 定义TypeLambda描述转换关系:输入In1,输出{ output: In1 } interface XformLambda extends TypeLambda { readonly type: { output: this["In1"] }; } // 定义默认的Identity转换关系:输入In1,输出In1本身 interface IdentityLambda extends TypeLambda { readonly type: this["In1"]; } // ParentBuilder使用Kind<F, V>表示将类型映射F应用到输入V的结果 type ParentBuilder<F extends TypeLambda = IdentityLambda> = { mod: <F2 extends TypeLambda>(transformer: <V>(v: V) => Kind<F2, V>) => ParentBuilder<F2>; child: <V>(value: V) => ChildBuilder<V, Kind<F, V>>; }; type ParentBuilderDef<F extends TypeLambda> = { transformer: <V>(v: V) => Kind<F, V>; }; const parentBuilder = <F extends TypeLambda>(def: ParentBuilderDef<F>): ParentBuilder<F> => ({ mod: (transformer) => parentBuilder({ ...def, transformer, }), child: (value) => childBuilder({ transformer: def.transformer, value, }), }); // ChildBuilder部分保持不变 type ChildBuilder<V, M> = { build: () => M; mod: <M2>(transformer: (val: V) => M2) => ChildBuilder<V, M2>; }; type ChildBuilderDef<V, M> = { value: V; transformer: (val: V) => M; }; const childBuilder = <V, M>(def: ChildBuilderDef<V, M>): ChildBuilder<V, M> => ({ mod: (transformer) => childBuilder({ ...def, transformer, }), build: () => def.transformer(def.value), }); const myBuilder = parentBuilder({ transformer: <V>(v: V) => v as Kind<IdentityLambda, V>, }); function xform<T>(x: T) { return { output: x }; } const configuredBuilder1 = myBuilder .child('hello' as const) .mod(xform) .build(); // ✅ 类型:{ output: 'hello' } const configuredBuilder2 = myBuilder .mod(<V>(v: V) => xform(v) as Kind<XformLambda, V>) .child('hello' as const) .build(); // ✅ 类型:{ output: 'hello' }
关键说明
TypeLambda是Effect-TS中描述类型映射的接口,通过In1定义输入类型,type定义输出类型Kind<F, V>表示将类型映射规则F应用到输入类型V,得到最终输出类型- 该方案适合复杂场景下的类型推导,类型关系更清晰规范
内容的提问来源于stack exchange,提问作者Max Liefkes
相关产品推荐
相关产品推荐

