如何推断TypeScript中重载回调的参数类型?
类型安全的Node.js风格回调实现方案
你遇到的问题源于TypeScript对重载函数回调的类型推断限制:当你定义重载接口时,传入的回调会被视为兼容所有重载的"最宽"类型,导致data无法在!err时自动窄化为T。以下是几种可行的解决方案:
方案一:使用联合类型回调配合类型守卫
将回调定义为两个函数类型的联合,让TypeScript能通过err的存在性自动窄化data的类型:
// 定义联合类型的回调:要么带错误,要么带数据 type SafeCallback<T> = ((err: Error) => void) | ((err: null, data: T) => void); const subscribe = <T>(callback: SafeCallback<T>) => { let result: T; try { // 模拟业务逻辑获取结果 result = { id: "ts-123" } as T; } catch (e) { // 将捕获到的错误转为Error类型后调用回调 callback(e as Error); return; } // 无错误时传入null和结果 callback(null, result); }; // 使用示例:类型推断正常 subscribe<{ id: string }>((err, data) => { if (err) { console.error("出错了:", err.message); } else { // 这里data会被自动推断为{ id: string },无unknown报错 console.log("数据ID:", data.id); } });
方案二:单一函数类型配合严格空值检查
如果偏好单一函数签名,可通过Error | null作为错误类型,结合严格空值检查来实现类型安全:
type SafeCallback<T> = (err: Error | null, data: T | undefined) => void; const subscribe = <T>(callback: SafeCallback<T>) => { let result: T; try { result = { id: "ts-456" } as T; } catch (e) { callback(e as Error, undefined); return; } callback(null, result); }; subscribe<{ id: string }>((err, data) => { if (err === null) { // 结合非空断言或额外判断确保data存在 console.log("数据ID:", data!.id); // 更严谨的写法: // if (data) console.log(data.id); } else { console.error("出错了:", err.message); } });
方案三:利用条件类型实现强关联
通过TypeScript条件类型,让data的类型完全依赖于err的类型,确保无错误时data必然为T:
// 条件类型:若err为null/undefined,则data为T;否则data为never type SafeCallback<T> = <Err>(err: Err, data: Err extends null | undefined ? T : never) => void; const subscribe = <T>(callback: SafeCallback<T>) => { let result: T; try { result = { id: "ts-789" } as T; } catch (e) { callback(e as Error, undefined as never); return; } callback(null, result); }; subscribe<{ id: string }>((err, data) => { if (!err) { console.log("数据ID:", data.id); // data类型自动推断为{ id: string } } else { console.error("出错了:", err.message); } });
为什么原方案失效?
你之前的接口重载写法中,TypeScript会将传入的回调视为兼容所有重载的签名,即(err: unknown | undefined, data?: T) => void,导致data的类型被合并为unknown | T,无法通过!err的判断自动窄化。而联合类型或条件类型的写法,能让TypeScript更精准地关联err和data的类型关系。
内容的提问来源于stack exchange,提问作者Diego Ferri
相关产品推荐
相关产品推荐

