TypeScript: problemy z systemem typów

101

Właśnie testuję maszynopis w VisualStudio 2012 i mam problem z jego systemem typów. Moja witryna HTML zawiera tag Canvas z identyfikatorem „mycanvas”. Próbuję narysować prostokąt na tym płótnie. Oto kod

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

Niestety VisualStudio narzeka na to

właściwość „getContext” nie istnieje w wartości typu „HTMLElement”

Oznacza drugą linię jako błąd. Myślałem, że to tylko ostrzeżenie, ale kod się nie kompiluje. VisualStudio tak mówi

wystąpiły błędy kompilacji. Czy chcesz kontynuować i uruchomić ostatnią udaną kompilację?

W ogóle nie podobał mi się ten błąd. Dlaczego nie ma dynamicznego wywołania metody? W końcu metoda getContext na pewno istnieje w moim elemencie canvas. Pomyślałem jednak, że ten problem będzie łatwy do rozwiązania. Właśnie dodałem adnotację typu dla płótna:

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

Ale system typów nadal nie był zadowolony. Oto nowy komunikat o błędzie, tym razem w pierwszym wierszu:

Nie można przekonwertować „HTMLElement” na „HTMLCanvasElement”: w typie „HTMLElement” brakuje właściwości „toDataURL” z typu „HTMLCanvasElement”

Cóż, wolę statyczne pisanie, ale to sprawia, że ​​język jest bezużyteczny. Czego oczekuje ode mnie system typów?

AKTUALIZACJA:

Maszynopis rzeczywiście nie obsługuje dynamicznych wywołań, a mój problem można rozwiązać za pomocą typecastów. Moje pytanie jest w zasadzie duplikatem tego TypeScript: casting HTMLElement

lhk
źródło

Odpowiedzi:

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

lub używając dynamicznego wyszukiwania z anytypem (bez sprawdzania typów):

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

Można spojrzeć na różne rodzaje w lib.d.ts .

Markus Jarderot
źródło
9
Warto wspomnieć, że w kontekście płótna lepiej jest używać CanvasRenderingContext2Dzamiast tekstu any.
Ivan Kochurkin
3
Od TypeScript 1.8 będzie rozpoznawać argument będący ciągiem stałym "2d"i .getContext("2d")zwróci ten typ CanvasRenderingContext2D. Nie musisz go rzucać jawnie.
Markus Jarderot
13
const canvas =  document.getElementById('stage') as HTMLCanvasElement;
Halil Kocaerkek
źródło
7
Proszę wyjaśnić, dlaczego ten kod pomaga rozwiązać problem OP, a nie tylko odpowiedź na kod. Czym różni się twój kod, jak to pomaga?
Kopiujesz / wklejasz i to działa. Co tu wyjaśnić? Jeśli to nie zadziała, wtedy ty powinien wyjaśnić problem i poprosić dokładniej.
lenooh
6

Podczas gdy inne odpowiedzi promują asercje typu (tym właśnie są - TypeScript nie ma rzutów typu, które faktycznie zmieniają typ; są one jedynie sposobem na wyeliminowanie błędów sprawdzania typu), intelektualnie uczciwym sposobem podejścia do problemu jest wysłuchanie komunikaty o błędach.

W Twoim przypadku są 3 rzeczy, które mogą pójść nie tak:

  • document.getElementById("mycanvas")może powrócić null, ponieważ nie znaleziono węzła o tym id (nazwa mogła zostać zmieniona, nie została jeszcze wstrzyknięta do dokumentu, ktoś mógł próbować uruchomić twoją funkcję w środowisku bez dostępu do DOM)
  • document.getElementById("mycanvas") może zwrócić odwołanie do elementu DOM, ale ten element DOM nie jest HTMLCanvasElement
  • document.getElementById("mycanvas")zwrócił prawidłową wartość HTMLElement, rzeczywiście jest to HTMLCanvasElement, ale CanvasRenderingContext2Dnie jest obsługiwana przez przeglądarkę.

Zamiast nakazać kompilatorowi zamknięcie się (i prawdopodobnie znaleźć się w sytuacji, w której Cannot read property 'getContext' of nullwyrzucany jest bezużyteczny komunikat o błędzie ), zalecam przejęcie kontroli nad granicami aplikacji.

Upewnij się, że element zawiera 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;
}

Upewnij się, że kontekst renderowania jest obsługiwany przez przeglądarkę

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

Stosowanie:

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

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

Zobacz TypeScript Playground .

Karol Majewski
źródło
1

Miałem ten sam problem, ale z SVGSVGElement zamiast HTMLCanvasElement. Przesyłanie do SVGSVGElement spowodowało błąd w czasie kompilacji:

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

Nie można przekonwertować „HTMLElement” na „SVGSVGElement”: w
typie „HTMLElement” brakuje właściwości „width” z typu „SVGSVGElement”.
W typie „SVGSVGElement” brakuje właściwości „onmouseleave” z typu „HTMLElement”.

Jeśli naprawiono to przez pierwsze rzutowanie na „any”:

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

lub w ten sposób (ma identyczny efekt)

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

Teraz mySvg jest silnie wpisane jako SVGSVGElement.

Edward
źródło
0

To stary temat ... może martwy do 2012 roku, ale ekscytujący i nowy w VS Code i maszynopisie.

Musiałem wykonać następujące czynności, aby to działało w programie VS Code z następującymi odwołaniami do pakietów.

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

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

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

Wersja maszynopisu:

{
    "@typescript-eslint/eslint-plugin": "^2.29.0",
    "@typescript-eslint/parser": "^2.29.0",
    "typescript": "^3.7.5"
}
anAgent
źródło
0

Być może będziesz musiał dodać DOMdo compilerOptions.libswojego tsconfig.json.

// 'tsconfig.json'
 {
  "compilerOptions": {
    "target": "ES2017",
    "module": "commonjs",
    "lib": [
      "es5",
      "DOM",
      "esnext"
    ]
  }
}
Barakat Turki
źródło