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

TypeScript泛型函数中如何保持关联类型间的连接?

TypeScript泛型中保持Action类型与参数关联的解决方案

核心问题:泛型函数中丢失type与value的类型关联

你的代码中,runAction函数使用<FAction extends FormAction>作为泛型约束时,TypeScript无法追踪formAction.type对应的setter参数类型和formAction.value的关联——因为FormAction是通过分布式条件类型生成的联合类型,泛型参数FAction可以是该联合类型的任意子集,编译器无法确定二者的对应关系。

解决方案:绑定泛型到具体的ActionType

修改runAction的泛型参数,直接绑定到ActionType,让formAction的类型明确对应单个ActionType的FormAction:

function runAction<ActionT extends ActionType>(
  formAction: FormAction<ActionT>,
  form: CForm,
) {
  const setter = getSetter(formAction.type);
  setter(formAction.value, form); // 类型检查通过
}

这样TypeScript能明确追踪到ActionT对应的setter参数类型和formAction.value的匹配关系,因为FormAction<ActionT>是针对单个ActionT的具体类型,而非联合类型的模糊子集。


附带问题:移除getSetter中的类型断言

原getSetter依赖类型断言是因为Array.find无法在泛型场景下准确推断返回值类型。可以通过将actions数组转换为键值对映射对象,让TypeScript直接推断出类型关联,无需断言。

实现方式:创建action映射表

// 将actions数组转换为type -> action的映射对象
const actionMap = Object.fromEntries(
  actions.map(action => [action.type, action])
) as { [K in ActionType]: Extract<SingleAction, { type: K }> };

function getSetter<ActionT extends ActionType>(
  actionType: ActionT,
): ActionSetter<ActionT> {
  const actionItem = actionMap[actionType];
  if (!actionItem) {
    throw new Error("unknown action");
  }
  return actionItem.set; // 无需类型断言,类型自动匹配
}

原理说明

通过Object.fromEntries将数组转换为对象后,我们给actionMap添加了精确的类型定义:每个键是ActionType的成员,对应的值是该type对应的具体SingleAction。此时访问actionMap[actionType]时,TypeScript能直接推断出对应的action类型,自然就能正确返回ActionSetter<ActionT>类型的set函数,无需额外断言。


完整代码示例

type CForm = {
  config: Config;
};
type Config = {
  projectName: string | undefined;
};

// one action per field
const actions = [
  {
    type: "init",
    set: (value: CForm, _form: CForm) => value,
  },
  {
    type: "setProjectName",
    set: (value: string, form: CForm): CForm => ({
      ...form,
      config: {...form.config, projectName: value}
    }),
  },
] as const;

/** one of the entries in actions */
type SingleAction = (typeof actions)[number];
/** all available action types */
type ActionType = SingleAction["type"];
/** a specific action object from actions array */
type ActionItem<ActionT extends ActionType> = Extract<
  SingleAction,
  { type: ActionT }
>;
/** the setter function corresponding to the action type */
type ActionSetter<ActionT extends ActionType> = ActionItem<ActionT>["set"];

type FormAction<ActionT extends ActionType = ActionType> =
  ActionT extends unknown
    ? {
        type: ActionT;
        value: Parameters<ActionSetter<ActionT>>[0];
      }
    : never;

// 创建action映射表,替代find方法
const actionMap = Object.fromEntries(
  actions.map(action => [action.type, action])
) as { [K in ActionType]: Extract<SingleAction, { type: K }> };

function getSetter<ActionT extends ActionType>(
  actionType: ActionT,
): ActionSetter<ActionT> {
  const actionItem = actionMap[actionType];
  if (!actionItem) {
    throw new Error("unknown action");
  }
  return actionItem.set;
}

// 固定类型场景正常工作
const form: CForm = { config: { projectName: undefined } };
const formAction: FormAction<"setProjectName"> = {
  type: "setProjectName",
  value: "name",
};
const setter = getSetter(formAction.type);
setter(formAction.value, form);

// 泛型函数类型检查通过
function runAction<ActionT extends ActionType>(
  formAction: FormAction<ActionT>,
  form: CForm,
) {
  const setter = getSetter(formAction.type);
  setter(formAction.value, form);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 17:22:07