如何在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
关键改进点
- 泛型调度器:
createDispatcher使用泛型HandlersMap接收任意处理器字典,不再强制统一成Handlers类型。 - 类型安全调用:返回的调度器函数通过
Parameters和ReturnType工具类型,自动推导对应处理器的参数和返回值类型,调用时会严格检查参数是否匹配。 - 保留原有Handler定义:不需要修改
Handler的泛型约束,保留了各个处理器的参数和返回值的精确类型。
内容的提问来源于stack exchange,提问作者user32225
相关产品推荐
相关产品推荐

