问题历史:在JavaScript中,回调函数被广泛使用,但它们的签名常常不明确。在TypeScript中,应该明确指定参数类型和返回值类型,否则很容易导致类型安全的漏洞。
问题:不正确或宽松的回调类型化会导致参数和结果的类型不确定,复杂化上下文(this)的使用,破坏编译器的错误自动检查并增加重构的难度。
解决方案:必须明确地定义回调函数的类型, указать передаваемые параметров的类型,正确处理可选参数和返回值,当需要时明确指定上下文的类型。
代码示例:
type Callback = (error: Error | null, result?: string) => void; function doAsyncWork(data: string, cb: Callback): void { setTimeout(() => { if (data === '') cb(new Error('空字符串')); else cb(null, data.toUpperCase()); }, 100); }
关键特性:
如果没有指定回调的返回值类型会发生什么?
TypeScript将接受任何返回类型(例如,undefined,void,Promise),这可能在异步链中或返回“默认”值时导致惊喜。
type BadCallback = (data: string) => any; // 任何结果,缺乏控制
可以将回调写成Function或(...args: any[]) => any吗?
不可以。这将去掉所有类型保护,失去关于参数数量、类型和返回类型的信息。这种做法的成本比完全放弃TypeScript更高。
如何对带this上下文的函数进行类型化?
在函数签名中使用第一个this参数或通过bind进行强制转换。例如:
interface ClickCallback { (this: HTMLElement, event: MouseEvent): void; } const handler: ClickCallback = function (event) { this.textContent = 'ok'; };
在项目中,回调定义为(...args: any[]) => any。在业务逻辑更新时,签名发生了变化,回调不再传递所需参数,错误仅在生产中暴露出来。
优点:
缺点:
引入严格的类型:定义了回调的接口,明确指定了this的类型和返回值的类型。编译器在部署之前捕获错误,重构和bug修复变得更简单。
优点:
缺点: