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

如何基于联合类型参数定义泛型并实现类型安全的动作处理函数

问题描述

我们有如下类型定义:

class ActionFoo {
  action: 'foo';
  foo: string;
}
class ActionBar {
  action: 'bar';
  bar: number;
}

同时对应每个action字段有专属处理函数:

function fooHandler(action: ActionFoo) {
  // 处理逻辑
}
function barHandler(action: ActionBar) {
  // 处理逻辑
}

需求是编写一个工具函数统一处理这些动作场景,但当前实现存在类型推断错误:

type ActionHandler<T extends { action: string }, O = any> = (action: T) => O;

function handleAction<T extends { action: string }, O = any>(
  action: T,
  handlers: {[key in T['action']]: ActionHandler<T, O>}
) {
  const handler = handlers[action.action];
  if (handler) return handler(action);
  throw new Error('not implemented');
}

type Action = ActionFoo | ActionBar;

function run(action: Action) {
  return handleAction(action, {
    foo: action => fooHandler(action), // TS报错:缺少`foo`字段(TS2345)
    bar: action => barHandler(action), // 类似类型错误
  });
}

请问如何基于传入的联合类型参数定义泛型,修复上述类型错误?


解决方案

原代码的问题在于泛型T被推断为整个联合类型ActionFoo | ActionBar,导致handlers的每个属性都要求接收联合类型参数,但实际每个handler只接受对应具体类型。我们需要让泛型针对联合类型中的每个成员单独约束,而非绑定整个联合。

修正后的完整代码如下:

// 定义动作类型的基础约束
type BaseAction = { action: string };

// 根据动作联合类型,映射出每个action对应的handler类型
type ActionHandlers<A extends BaseAction, O = any> = {
  [K in A['action']]: (action: Extract<A, { action: K }>) => O;
};

function handleAction<A extends BaseAction, O = any>(
  action: A,
  handlers: ActionHandlers<A, O>
): O {
  const handler = handlers[action.action as A['action']];
  if (handler) {
    // 类型断言确保编译器识别action与handler的匹配关系
    return handler(action as Extract<A, { action: typeof action.action }>);
  }
  throw new Error('not implemented');
}

type Action = ActionFoo | ActionBar;

function run(action: Action) {
  return handleAction(action, {
    foo: (action) => fooHandler(action), // 正确推断action为ActionFoo
    bar: (action) => barHandler(action), // 正确推断action为ActionBar
  });
}

关键改动说明

  1. 精准的handler类型映射:使用Extract<A, { action: K }>从联合类型A中提取出action字段等于K的具体子类型,确保每个handler的参数是对应单个动作类型,而非整个联合。
  2. 泛型约束调整:handleAction的泛型A约束为BaseAction,覆盖所有带action字段的类型,配合ActionHandlers实现精准的类型映射。
  3. 安全的类型断言:由于TypeScript对联合类型的自动窄化存在局限性,这里的as断言是安全的——因为handlers的结构已经保证了每个action对应正确的handler,不会出现类型不匹配的情况。

你也可以利用TypeScript的分布式条件类型进一步简化handler的类型定义:

type ActionHandler<A> = A extends { action: infer K } ? (action: A) => any : never;

type ActionHandlers<A extends BaseAction> = {
  [K in A['action']]: ActionHandler<Extract<A, { action: K }>>;
};

// handleAction函数实现保持不变

修改后,每个handler的参数类型都会被正确推断为对应的具体动作类型,原代码中的类型错误即可解决。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 11:52:53