TypeScript疑问:空值校验后传递ctx为何仍触发类型错误?
TypeScript已校验非null的变量在回调中仍触发类型错误?
问题重现
编写了如下TypeScript代码:
let ctx = canvas.getContext('2d') if (ctx === null) { throw new Error("ctx can not be null") } ctx.strokeStyle = 'black' canvas.addEventListener('mousedown', (e) => startDrawing(ctx, e))
运行时发现,设置ctx.strokeStyle = 'black'未出现类型错误,但在canvas.addEventListener的回调函数中,将ctx传入startDrawing函数时触发了TypeScript类型错误,错误信息如下:
Argument of type 'CanvasRenderingContext2D | null' is not assignable to parameter of type 'CanvasRenderingContext2D'. Type 'null' is not assignable to type 'CanvasRenderingContext2D'.
明明已经通过if语句校验ctx不为null,为何还会出现该错误?
原因分析
这是因为TypeScript的类型窄化存在作用域限制:
- 用
let声明的变量支持重新赋值,TS无法确保在异步执行的回调触发时,ctx没有被后续代码重新赋值为null。因此TS会在回调函数中恢复ctx的原始联合类型CanvasRenderingContext2D | null,而非之前通过if语句窄化后的非null类型。 - 而在
if语句之后的同步代码中,TS能确定ctx此时非null,所以设置strokeStyle不会报错。
解决办法
方法1:改用const声明ctx
将let改为const,const变量不可被重新赋值,TS能确定回调执行时ctx的类型依然是窄化后的非null类型:
const ctx = canvas.getContext('2d') if (ctx === null) { throw new Error("ctx can not be null") } ctx.strokeStyle = 'black' canvas.addEventListener('mousedown', (e) => startDrawing(ctx, e))
方法2:使用非空断言(谨慎使用)
如果必须使用let,可以在回调中用非空断言!明确告诉TSctx一定不为null,但要确保后续代码不会将ctx重新赋值为null:
let ctx = canvas.getContext('2d') if (ctx === null) { throw new Error("ctx can not be null") } ctx.strokeStyle = 'black' canvas.addEventListener('mousedown', (e) => startDrawing(ctx!, e))
方法3:回调内重新校验
在回调函数中再次校验ctx是否为null,虽然冗余,但能确保绝对的类型安全:
let ctx = canvas.getContext('2d') if (ctx === null) { throw new Error("ctx can not be null") } ctx.strokeStyle = 'black' canvas.addEventListener('mousedown', (e) => { if (ctx) { startDrawing(ctx, e) } })
内容的提问来源于stack exchange,提问作者CyberNinjaX
相关产品推荐
相关产品推荐

