You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 13:22:14