TypeScript: vấn đề với hệ thống kiểu


101

Tôi chỉ đang kiểm tra bản định kiểu trong VisualStudio 2012 và gặp sự cố với hệ thống kiểu của nó. Trang web html của tôi có một thẻ canvas với id "mycanvas". Tôi đang cố vẽ một hình chữ nhật trên canvas này. Đây là mã

var canvas = document.getElementById("mycanvas");
var ctx: CanvasRenderingContext2D = canvas.getContext("2d");
ctx.fillStyle = "#00FF00";
ctx.fillRect(0, 0, 100, 100);

Thật không may, VisualStudio phàn nàn rằng

thuộc tính 'getContext' không tồn tại trên giá trị của loại 'HTMLElement'

Nó đánh dấu dòng thứ hai là một lỗi. Tôi nghĩ đây sẽ chỉ là một cảnh báo nhưng mã không biên dịch. VisualStudio nói rằng

có lỗi xây dựng. Bạn có muốn tiếp tục và chạy bản dựng thành công cuối cùng không?

Tôi không thích lỗi này chút nào. Tại sao không có lệnh gọi phương thức động? Sau tất cả, phương thức getContext chắc chắn tồn tại trên phần tử canvas của tôi. Tuy nhiên tôi nghĩ vấn đề này sẽ dễ giải quyết. Tôi vừa thêm một chú thích kiểu cho canvas:

var canvas : HTMLCanvasElement = document.getElementById("mycanvas");
var ctx: CanvasRenderingContext2D = canvas.getContext("2d");
ctx.fillStyle = "#00FF00";
ctx.fillRect(0, 0, 100, 100);

Nhưng hệ thống loại vẫn không hài lòng. Đây là thông báo lỗi mới, lần này là ở dòng đầu tiên:

Không thể chuyển đổi 'HTMLElement' thành 'HTMLCanvasElement': Loại 'HTMLElement' bị thiếu thuộc tính 'toDataURL' từ loại 'HTMLCanvasElement'

Chà, tôi sẵn sàng cho việc nhập tĩnh nhưng điều này khiến ngôn ngữ không sử dụng được. Loại hệ thống muốn tôi làm gì?

CẬP NHẬT:

Typecript thực sự không hỗ trợ cho lệnh gọi động và vấn đề của tôi có thể được giải quyết bằng typecast. Câu hỏi của tôi về cơ bản là một bản sao của TypeScript này : truyền HTMLElement

Câu trả lời:


223
var canvas = <HTMLCanvasElement> document.getElementById("mycanvas");
var ctx = canvas.getContext("2d");

hoặc sử dụng tra cứu động với anyloại (không đánh máy):

var canvas : any = document.getElementById("mycanvas");
var ctx = canvas.getContext("2d");

Bạn có thể nhìn vào các loại khác nhau trong lib.d.ts .


9
Điều đáng nói là tốt hơn nên sử dụng CanvasRenderingContext2Dthay vì anyloại cho ngữ cảnh canvas.
Ivan Kochurkin

3
Kể từ TypeScript 1.8, nó sẽ nhận ra đối số chuỗi không đổi "2d".getContext("2d")sẽ trả về kiểu CanvasRenderingContext2D. Bạn không cần phải truyền nó một cách rõ ràng.
Markus Jarderot

13
const canvas =  document.getElementById('stage') as HTMLCanvasElement;

7
Vui lòng giải thích tại sao mã này giúp giải quyết vấn đề của OP thay vì chỉ là một câu trả lời mã. Mã của bạn làm khác đi điều gì, nó giúp ích gì?

Bạn sao chép / dán và nó hoạt động. Có gì để giải thích ở đây? Nếu nó không hiệu quả với bạn, thì bạn nên giải thích vấn đề của mình và hỏi cụ thể hơn.
lenooh

6

Trong khi các câu trả lời khác thúc đẩy xác nhận kiểu (chính là như vậy - TypeScript không có các phôi kiểu thực sự thay đổi kiểu; chúng chỉ là một cách để loại bỏ lỗi kiểm tra kiểu), thì cách trung thực về mặt trí tuệ để tiếp cận vấn đề của bạn là lắng nghe thông báo lỗi.

Trong trường hợp của bạn, có 3 điều có thể sai:

  • document.getElementById("mycanvas")có thể trả về null, vì không tìm thấy nút nào của id đó (nó có thể đã được đổi tên, chưa được đưa vào tài liệu, ai đó có thể đã thử chạy hàm của bạn trong môi trường không có quyền truy cập vào DOM)
  • document.getElementById("mycanvas") có thể trả về một tham chiếu đến phần tử DOM, nhưng phần tử DOM này không phải là HTMLCanvasElement
  • document.getElementById("mycanvas")đã trả về một giá trị hợp lệ HTMLElement, nó thực sự là một HTMLCanvasElement, nhưng CanvasRenderingContext2Dkhông được trình duyệt hỗ trợ.

Thay vì yêu cầu trình biên dịch đóng cửa (và có thể thấy mình ở trong tình huống mà một thông báo lỗi vô ích như Cannot read property 'getContext' of nullđược ném ra), tôi khuyên bạn nên kiểm soát ranh giới ứng dụng của mình.

Đảm bảo phần tử chứa 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;
}

Đảm bảo rằng ngữ cảnh kết xuất được trình duyệt hỗ trợ

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;
}

Sử dụng:

const ctx: CanvasRenderingContext2D = getCanvasRenderingContext2D(getCanvasElementById('mycanvas'))

ctx.fillStyle = "#00FF00";
ctx.fillRect(0, 0, 100, 100);

Xem Sân chơi TypeScript .



1

Tôi đã gặp vấn đề tương tự, nhưng với SVGSVGElement thay vì HTMLCanvasElement. Truyền tới SVGSVGElement đã xảy ra lỗi thời gian biên dịch:

var mySvg = <SVGSVGElement>document.getElementById('mySvg');

Không thể chuyển đổi 'HTMLElement' thành 'SVGSVGElement':
Loại 'HTMLElement' bị thiếu thuộc tính 'width' từ loại 'SVGSVGElement'.
Loại 'SVGSVGElement' bị thiếu thuộc tính 'onmouseleave' từ loại 'HTMLElement'.

Nếu sửa nó bằng cách truyền lần đầu thành 'bất kỳ':

var mySvg = <SVGSVGElement><any>document.getElementById('mySvg');

hoặc theo cách này (nó có tác dụng giống hệt nhau)

var mySvg: SVGSVGElement = <any>document.getElementById('mySvg');

Bây giờ mySvg được gõ mạnh là SVGSVGElement.


0

Đây là một chủ đề cũ ... có thể đã chết từ năm 2012, nhưng thú vị và mới mẻ đối với VS Code và typecript.

Tôi đã phải làm như sau để điều này hoạt động trong VS Code với các tham chiếu gói sau.

const demoCanvas: HTMLCanvasElement = document.getElementById('rfrnCanvas') as any;

        if(demoCanvas.getContext) {
            const context = demoCanvas.getContext('2d');

            if(context) {
                reactangle(context);
            }
        }

Phiên bản Typecript:

{
    "@typescript-eslint/eslint-plugin": "^2.29.0",
    "@typescript-eslint/parser": "^2.29.0",
    "typescript": "^3.7.5"
}

0

Bạn có thể phải thêm DOMvào compilerOptions.libtrong của bạn tsconfig.json.

// 'tsconfig.json'
 {
  "compilerOptions": {
    "target": "ES2017",
    "module": "commonjs",
    "lib": [
      "es5",
      "DOM",
      "esnext"
    ]
  }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.