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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 12:44:58