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

如何在TypeScript中声明泛型函数字典?

问题描述

我想创建一系列基于TypeScript泛型的Handler类型函数,每个函数的参数和返回值都由泛型明确定义。这些函数组成的字典会传入dispatcher函数,该函数根据传入的处理器名称和参数,调用对应函数并返回结果。我基于CRUD(含列表)操作声明了函数,但始终无法解决最后一步的类型兼容错误,错误信息见代码注释。

相关代码:

type ID = string;
type VALUE = string | number | boolean | Date;
type AnyRow = Record<string, VALUE>;

// 默认的Id、In和Out类型
type DefaultId = ID | undefined;
type DefaultIn = AnyRow | undefined;
// 列表操作始终返回数组(可能为空),不会返回undefined;读取操作可能返回行数据或undefined
type DefaultOut = AnyRow[] | (AnyRow | undefined);

// Handler类型:可能接收Id,也可能不接收;输入(In)和输出(Out)类型多样
// 注:实际返回值应为Promise,还有第三个参数(如排序、分页选项)这里简化了
export type Handler<
  Id extends DefaultId,
  In extends DefaultIn,
  Out extends DefaultOut
> = (params: { id: Id; data: In }) => Out;

export type Handlers = Record<
  string,
  Handler<DefaultId, DefaultIn, DefaultOut>
>;

// 创建调度器:接收处理器字典,返回一个函数,根据名称调用对应处理器
export const createDispatcher =
  (handlers: Handlers) =>
  (fnName: string, requestData: { id: DefaultId; data: DefaultIn }) =>
    handlers[fnName](requestData);

// CRUD+列表操作的处理器类型,代表最常见的输入输出组合
type Resolvers<T extends AnyRow> = {
  list: Handler<undefined, undefined, T[] | undefined>;
  create: Handler<undefined, T, T>;
  read: Handler<ID, undefined, T | undefined>;
  update: Handler<ID, T, T>;
  delete: Handler<ID, undefined, undefined>;
};

type Data = { a: number };

const handlers: Resolvers<Data> = {
  list: () => [{ a: 1 }, { a: 2 }],
  read: ({ id }) => (id ? { a: 1 } : { a: 2 }),
  create: ({ data }) => data,
  update: ({ id, data }) => (id ? data : data),
  delete: ({ id }) => (id ? undefined : undefined),
};

// 错误位置:创建Data对应的调度器
export const dataDispatcher = createDispatcher(handlers);
//                                             ^^^^^^^^
// 错误信息:
// Argument of type 'Resolvers<Data>' is not assignable to parameter of type 'Handlers'.
//   Property 'list' is incompatible with index signature.
//     Type 'Handler<undefined, undefined, Data[] | undefined>' is not assignable to type 'Handler<DefaultId, DefaultIn, DefaultOut>'.
//       Type 'DefaultId' is not assignable to type 'undefined'.
//         Type 'string' is not assignable to type 'undefined'.
问题原因

核心是函数参数的逆变规则:

  • Handler<undefined, undefined, ...> 要求调用时传入的params.id必须是undefined,属于参数类型更严格的函数。
  • 而Handlers中的Handler<DefaultId, DefaultIn, ...>允许params.id是string | undefined,属于参数类型更宽泛的函数。
  • TypeScript禁止将严格参数类型的函数赋值给宽泛参数类型的函数——如果强行赋值,调度器可能会传入string类型的id给list处理器,而list处理器只接受undefined,会导致运行时错误。
解决方案

把createDispatcher改成泛型函数,让它能接受任意处理器字典,同时保证调用时的类型安全。TypeScript会自动推导每个处理器的参数和返回值,避免强制统一成宽泛的Handlers类型。

修正后的代码:

type ID = string;
type VALUE = string | number | boolean | Date;
type AnyRow = Record<string, VALUE>;

type DefaultId = ID | undefined;
type DefaultIn = AnyRow | undefined;
type DefaultOut = AnyRow[] | (AnyRow | undefined);

export type Handler<
  Id extends DefaultId,
  In extends DefaultIn,
  Out extends DefaultOut
> = (params: { id: Id; data: In }) => Out;

// 把createDispatcher改为泛型函数,接受任意HandlersMap类型
export const createDispatcher = <HandlersMap extends Record<string, Handler<any, any, any>>>(
  handlers: HandlersMap
) => {
  // 返回的调度器函数也用泛型,约束fnName必须是HandlersMap的键,requestData匹配对应Handler的参数
  return <FnName extends keyof HandlersMap>(
    fnName: FnName,
    requestData: Parameters<HandlersMap[FnName]>[0]
  ): ReturnType<HandlersMap[FnName]> => {
    return handlers[fnName](requestData);
  };
};

type Resolvers<T extends AnyRow> = {
  list: Handler<undefined, undefined, T[] | undefined>;
  create: Handler<undefined, T, T>;
  read: Handler<ID, undefined, T | undefined>;
  update: Handler<ID, T, T>;
  delete: Handler<ID, undefined, undefined>;
};

type Data = { a: number };

const handlers: Resolvers<Data> = {
  list: () => [{ a: 1 }, { a: 2 }],
  read: ({ id }) => (id ? { a: 1 } : { a: 2 }),
  create: ({ data }) => data,
  update: ({ id, data }) => (id ? data : data),
  delete: ({ id }) => (id ? undefined : undefined),
};

// 现在类型完全兼容,且调用调度器时会有严格的类型提示
export const dataDispatcher = createDispatcher(handlers);

// 调用示例:类型检查生效
dataDispatcher('list', { id: undefined, data: undefined }); // 正确
dataDispatcher('read', { id: '123', data: undefined }); // 正确
// dataDispatcher('list', { id: '123', data: undefined }); // 错误:list的id必须是undefined
关键改进点
  1. 泛型调度器:createDispatcher使用泛型HandlersMap接收任意处理器字典,不再强制统一成Handlers类型。
  2. 类型安全调用:返回的调度器函数通过Parameters和ReturnType工具类型,自动推导对应处理器的参数和返回值类型,调用时会严格检查参数是否匹配。
  3. 保留原有Handler定义:不需要修改Handler的泛型约束,保留了各个处理器的参数和返回值的精确类型。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 21:01:06