TypeScript泛型返回类型与参数不匹配问题求解
类型安全实现依赖参数类型的泛型回调函数
问题背景
我尝试为一个泛型函数添加类型定义,该函数的泛型返回值依赖于参数类型,简化后的代码如下:
type QueueFlushFailAction = { "duplicate-file-name": "open-existing-on-collision" | "remove-from-queue"; "no-connection": "reconnect" | "continue"; }; type FileFlushFailCallback = <T extends keyof QueueFlushFailAction>( file: any, error: T ) => Promise<QueueFlushFailAction[T]>; const fileFlushFailCallback: FileFlushFailCallback = async (file, error) => { if (error === "duplicate-file-name") { if (file.exists) return "open-existing-on-collision"; else return "remove-from-queue"; } if (error === "no-connection") { if (file.retryCount < 3) return "reconnect"; else return "continue"; } return 'continue'; };
运行后出现如下错误:
Type '<T extends keyof QueueFlushFailAction>(file: any, error: T) => Promise<"open-existing-on-collision" | "remove-from-queue" | "reconnect" | "continue">' is not assignable to type 'FileFlushFailCallback'. Type 'Promise<"open-existing-on-collision" | "remove-from-queue" | "reconnect" | "continue">' is not assignable to type 'Promise<QueueFlushFailAction[T]>'. Type '"open-existing-on-collision" | "remove-from-queue" | "reconnect" | "continue"' is not assignable to type 'QueueFlushFailAction[T]'. Type '"open-existing-on-collision"' is not assignable to type 'QueueFlushFailAction[T]'. Type 'string' is not assignable to type 'never'.(2322)
我想知道是否存在类型安全的方式,通过泛型或其他机制实现该类型定义?已知函数重载能实现类似效果,但FileFlushFailCallback类型位于库中,实现则在库使用者的代码里,而TypeScript的函数重载要求实现与声明在同一文件中,因此无法使用该方案。
解决方案
原因分析
原代码报错的核心是:TypeScript无法自动推断出每个分支的返回值与泛型参数T的对应关系。泛型函数要求实现对任意T都返回对应类型,而当前实现返回的是所有可能值的联合类型,无法匹配特定T的约束。
方案一:条件分支断言(兼容现有库类型)
如果无法修改库中的FileFlushFailCallback类型,可以在每个分支的返回值处添加类型断言,明确告知TS当前返回值匹配对应T的类型:
type QueueFlushFailAction = { "duplicate-file-name": "open-existing-on-collision" | "remove-from-queue"; "no-connection": "reconnect" | "continue"; }; type FileFlushFailCallback = <T extends keyof QueueFlushFailAction>( file: any, error: T ) => Promise<QueueFlushFailAction[T]>; const fileFlushFailCallback: FileFlushFailCallback = async (file, error) => { if (error === "duplicate-file-name") { const result = file.exists ? "open-existing-on-collision" : "remove-from-queue"; // 断言为当前error对应的返回类型 return result as QueueFlushFailAction[typeof error]; } if (error === "no-connection") { const result = file.retryCount < 3 ? "reconnect" : "continue"; return result as QueueFlushFailAction[typeof error]; } // 兜底分支,理论上不会触发,断言为对应类型 return 'continue' as QueueFlushFailAction[T]; };
这种方式保证了类型安全,因为每个分支的断言都是基于当前error的具体类型,TS可以正确验证返回值的合法性。
方案二:映射类型生成函数签名(推荐,需修改库类型)
如果可以修改库中的FileFlushFailCallback类型定义,改用映射类型生成函数签名联合的方式,无需断言即可实现完全类型安全:
type QueueFlushFailAction = { "duplicate-file-name": "open-existing-on-collision" | "remove-from-queue"; "no-connection": "reconnect" | "continue"; }; // 通过映射类型为每个错误类型生成对应的函数签名,再取联合类型 type FileFlushFailCallback = { [T in keyof QueueFlushFailAction]: ( file: any, error: T ) => Promise<QueueFlushFailAction[T]>; }[keyof QueueFlushFailAction]; const fileFlushFailCallback: FileFlushFailCallback = async (file, error) => { if (error === "duplicate-file-name") { return file.exists ? "open-existing-on-collision" : "remove-from-queue"; } if (error === "no-connection") { return file.retryCount < 3 ? "reconnect" : "continue"; } return 'continue'; };
该方案通过映射类型自动生成每个错误类型对应的函数签名,TS会自动推断每个分支的返回值匹配对应的类型,实现完全类型安全,同时对使用者代码无侵入。
内容的提问来源于stack exchange,提问作者nikitakot
相关产品推荐
相关产品推荐

