TypeScript: problems with type system
TypeScript: problems with type system
我只是在VisualStudio 2012中测试TypeScript,并且遇到了它的类型系统的问题。我的HTML网站有一个带有id“mycanvas”的canvas标签。我试图在这个canvas上画一个矩形。以下是代码:
var canvas = document.getElementById("mycanvas"); var ctx: CanvasRenderingContext2D = canvas.getContext("2d"); ctx.fillStyle = "#00FF00"; ctx.fillRect(0, 0, 100, 100);
不幸的是,VisualStudio抱怨说:
在类型为'HTMLElement'的值上不存在属性'getContext'
它将第二行标记为错误。我以为这只是一个警告,但代码无法编译。VisualStudio说:
有构建错误。您是否要继续运行上次成功的构建?
我不喜欢这个错误。为什么没有动态方法调用?毕竟,getContext方法在我的canvas元素上绝对存在。然而,我认为这个问题应该很容易解决。我只需为canvas添加一个类型注释:
var canvas : HTMLCanvasElement = document.getElementById("mycanvas"); var ctx: CanvasRenderingContext2D = canvas.getContext("2d"); ctx.fillStyle = "#00FF00"; ctx.fillRect(0, 0, 100, 100);
但是类型系统仍然不满意。这次是在第一行显示新的错误消息:
无法将'HTMLElement'转换为'HTMLCanvasElement':类型'HTMLElement'缺少类型'HTMLCanvasElement'的属性'toDataURL'
好吧,我对静态类型很失望,这使得语言无法使用。类型系统想让我怎么办呢?
更新:
Typescript确实不支持动态调用,我的问题可以通过类型转换解决。我的问题基本上是这个问题的重复:TypeScript: casting HTMLElement。
TypeScript: 类型系统的问题
在其他答案中,提倡使用类型断言(那就是它们的用途 - TypeScript没有实际更改类型的类型转换;它们只是一种抑制类型检查错误的方式),在处理问题时,诚实的方式是听取错误消息。
在您的情况下,有3个可能出错的地方:
- `document.getElementById("mycanvas")`可能返回`null`,因为找不到该id的节点(它可能已经更名,未注入到文档中,或者可能在没有访问DOM权限的环境中尝试运行您的函数)。
- `document.getElementById("mycanvas")`可能返回对DOM元素的引用,但是此DOM元素不是`HTMLCanvasElement`。
- `document.getElementById("mycanvas")`确实返回了一个有效的`HTMLElement`,它确实是一个`HTMLCanvasElement`,但是浏览器不支持`CanvasRenderingContext2D`。
与其告诉编译器闭嘴(可能会发现自己处于一个无用错误消息的情况,例如抛出`Cannot read property 'getContext' of null`),我建议控制您的应用程序边界。
确保元素包含HTMLCanvasElement
const getCanvasElementById = (id: string): HTMLCanvasElement => { const canvas = document.getElementById(id); if (!(canvas instanceof HTMLCanvasElement)) { throw new Error(`The element of id "${id}" is not a HTMLCanvasElement. Make sure a <canvas id="${id}""> element is present in the document.`); } return canvas; }
确保浏览器支持渲染上下文
const getCanvasRenderingContext2D = (canvas: HTMLCanvasElement): CanvasRenderingContext2D => { const context = canvas.getContext('2d'); if (context === null) { throw new Error('This browser does not support 2-dimensional canvas rendering contexts.'); } return context; }
使用方法:
const ctx: CanvasRenderingContext2D = getCanvasRenderingContext2D(getCanvasElementById('mycanvas')) ctx.fillStyle = "#00FF00"; ctx.fillRect(0, 0, 100, 100);
在上述代码中,我们使用了两种不同的方法来获取canvas元素的上下文对象。第一种方法使用了类型断言<HTMLCanvasElement>
,将返回值转换为HTMLCanvasElement
类型,然后再调用getContext("2d")
方法。第二种方法中,我们将canvas对象的类型声明为any
,而不进行类型检查,直接调用getContext("2d")
方法。
上述代码的问题在于,第一种方法中的类型断言可能导致类型不匹配的错误。如果我们错误地将一个非HTMLCanvasElement
类型的对象转换为HTMLCanvasElement
类型,那么在调用getContext("2d")
方法时就会出现类型错误。而第二种方法中,由于将canvas对象的类型声明为any
,TypeScript将不会对其进行类型检查,因此无法发现类型错误。
解决这个问题的方法是,应该尽量避免使用类型断言来进行类型转换,而是使用更准确的类型声明。对于获取canvas上下文对象,推荐使用CanvasRenderingContext2D
类型来声明canvas对象的类型。在TypeScript 1.8及以后的版本中,.getContext("2d")
方法会根据常量字符串参数"2d"
来自动推断返回类型为CanvasRenderingContext2D
,因此不需要显式地进行类型转换。
为了避免类型错误,我们应该尽量避免使用类型断言进行类型转换,而是使用准确的类型声明,特别是在获取canvas上下文对象时。使用CanvasRenderingContext2D
类型声明canvas对象的类型可以让TypeScript在编译时进行类型检查,以提前发现并修复类型错误。
问题出现的原因是在TypeScript中,getElementById方法返回的是HTMLElement类型,而不是HTMLCanvasElement类型。因此,需要使用类型断言(as)将返回值转换为HTMLCanvasElement类型。
解决方法是在获取元素的同时使用类型断言将其转换为正确的类型,如下所示:
const canvas = document.getElementById('stage') as HTMLCanvasElement;
这样,我们就可以在后续的代码中使用canvas变量,并且TypeScript会将其类型识别为HTMLCanvasElement类型,从而避免类型错误的问题。
以上是关于(TypeScript: problems with type system)问题的出现原因以及解决方法的整理。