起因
如下代码,我们调用document.getElementById
获取一个元素,然后尝试对其调用getContext
方法。然而这段代码无法通过ts
检查,而是会抛出错误。
const myCanvas = document.getElementById("canvas")
const context = myCanvas.getContext('2d');
错误1
'myCanvas' is possibly 'null'.ts(18047)
这个很好理解,错误信息告诉我们,myCanvas
的值可能为null
,页面上可能不存在id
为canvas
元素,这种情况下,document.getElementById
就会返回null
,当尝试对null
调用方法,报错也就很合理了。
这里我们加一个if
判断,就能轻松的解决这个问题:
const myCanvas = document.getElementById("canvas")
// 当myCanvas为真时(剔除了myCanvas为null的情况),我们才对其调用getContext方法。
if (myCanvas) {
const context = myCanvas.getContext('2d')
}
错误2
myCanvas
可能为null
的报错解决了,我们发现ts
又报了另一个错误。
Property 'getContext' does not exist on type 'HTMLElement'.ts(2339)
上面的错误信息告诉我们,HTMLElement
上并不存在getContext
方法,这是怎么回事呢?
实际上,标准委员会规定document.getElementById在找到元素时,会返回一个HTMLElement
元素,而HTMLElement
元素上是没有getContext
方法的。所以在HTMLElement
类型的元素上调用一个不存在的方法,ts
会报错,这很合理。
解决方案就是明确的告诉ts
,我们的代码会返回HTMLCanvasElement
类型,因为我们知道canvas
并不是一个普通的HTMLElement
类型元素,而是一个HTMLCanvasElement
类型元素,并且HTMLCanvasElement
类型元素上是存在getContext
方法的,也就能够合法的调用getContext
。
你可能会想,那我直接把myCanvas
声明为HTMLCanvasElement
类型不就行了?比如下列代码:
const myCanvas: HTMLCanvasElement = document.getElementById("main_canvas")
然而很遗憾,这是行不通的。这里我们直接将其转换为一个更简单的形式帮助理解:
const getString = ():string => {
return 'tomcat'
}
const num: number = getString();
// Type 'string' is not assignable to type 'number'.ts(2322)
上面的代码试图将一个string
结果赋值给一个number
类型的变量,这显然是无法通过ts
检查的,而这就和上面的代码是一样的道理。也就是我们无法通过类型声明来告诉ts
:myCanvas
是HTMLCanvasElement
类型。
那要怎么做呢?答案是断言。
const myCanvas = document.getElementById("canvas") as HTMLCanvasElement
const context = myCanvas.getContext('2d')
// or
const myCanvas = document.getElementById("canvas")
const context = (myCanvas as HTMLCanvasElement).getContext('2d')
上面的代码明确告诉ts
,document.getElementById
会返回一个HTMLCanvasElement
元素,而HTMLCanvasElement
上是存在getContext
方法的,所以后面的getContext
也就属于合法调用,也就不会报错了。
断言的问题
断言相当于移除了编译时的类型检查,也就无法在编译阶段检查出异常情况,所以如果你无法保证断言始终为真,就会导致运行时的异常。
const myCanvas = document.getElementById("canvas") as HTMLCanvasElement
const context = myCanvas.getContext('2d');
比如上面的断言代码,就要求document.getElementById("canvas")
始终返回一个有效的HTMLCanvasElement
的类型元素。而我们知道document.getElementById
返回null
的可能性是无法排除的。
当页面上没有canvas
元素,null
就会被断言为HTMLCanvasElement
,其结果就会导致异常。所以这里就要求我们用运行时的代码进行检测。
加上运行时检测:
const myCanvas = document.getElementById("canvas") as HTMLCanvasElement
if (myCanvas) {
const context = myCanvas.getContext('2d')
}
这样,就算null
被断言为HTMLCanvasElement
类型,在运行时,它仍然还是个null
,也就无法绕过if
语句进而导致异常了。
更好的方式
我们知道了断言的弊端,那有没有更好的方式呢?
答案是用类型守卫来取代避免断言。
const myCanvas = document.getElementById("canvas")
if (myCanvas instanceof HTMLCanvasElement) {
const context = myCanvas.getContext('2d')
}
这里其实做了两件事:
1.剔除了myCanvas
为null
的情况,因为null
的原型链上是不会有HTMLCanvasElement
的。
2.判断myCanvas
是否为HTMLCanvasElement
类型。
而且这里的代码意图更明显,也更容易理解,所以更推荐使用类型守卫而不是断言。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。