TypeScript: problèmes avec le système de types

101

Je teste juste du typographie dans VisualStudio 2012 et j'ai un problème avec son système de type. Mon site html a une balise canvas avec l'identifiant "mycanvas". J'essaye de dessiner un rectangle sur cette toile. Voici le code

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

Malheureusement, VisualStudio se plaint que

la propriété 'getContext' n'existe pas sur la valeur de type 'HTMLElement'

Il marque la deuxième ligne comme une erreur. Je pensais que ce serait simplement un avertissement mais le code ne compile pas. VisualStudio dit que

il y a eu des erreurs de construction. Souhaitez-vous continuer et exécuter la dernière version réussie?

Je n'ai pas du tout aimé cette erreur. Pourquoi n'y a-t-il pas d'appel de méthode dynamique? Après tout, la méthode getContext existe définitivement sur mon élément canvas. Cependant, je pensais que ce problème serait facile à résoudre. Je viens d'ajouter une annotation de type pour le canevas:

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

Mais le système de type n'était toujours pas satisfait. Voici le nouveau message d'erreur, cette fois sur la première ligne:

Impossible de convertir «HTMLElement» en «HTMLCanvasElement»: le type «HTMLElement» ne contient pas la propriété «toDataURL» du type «HTMLCanvasElement»

Eh bien, je suis tout à fait pour le typage statique mais cela rend le langage inutilisable. Qu'est-ce que le système de typage veut que je fasse?

METTRE À JOUR:

Typescript n'a en effet aucun support pour l'invocation dynamique et mon problème peut être résolu avec les typecasts. Ma question est essentiellement un double de celui-ci TypeScript: casting HTMLElement

lhk
la source

Réponses:

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

ou en utilisant la recherche dynamique avec le anytype (pas de vérification de type):

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

Vous pouvez regarder les différents types de lib.d.ts .

Markus Jarderot
la source
9
Il est à noter qu'il est préférable d'utiliser CanvasRenderingContext2Dau lieu de anytype pour le contexte de canevas.
Ivan Kochurkin
3
Depuis TypeScript 1.8, il reconnaîtra l'argument de chaîne constante "2d"et .getContext("2d")retournera avec le type CanvasRenderingContext2D. Vous n'avez pas besoin de le diffuser explicitement.
Markus Jarderot
13
const canvas =  document.getElementById('stage') as HTMLCanvasElement;
Halil Kocaerkek
la source
7
Veuillez expliquer pourquoi ce code aide à résoudre le problème d'OP plutôt qu'une simple réponse de code. Que fait votre code différemment, en quoi cela aide-t-il?
Vous copiez / collez et cela fonctionne. Qu'est-ce qu'il faut expliquer ici? Si cela ne fonctionne pas pour vous, vous devez expliquer votre problème et demander plus précisément.
lenooh
6

Alors que d'autres réponses promeuvent les assertions de type (c'est ce qu'elles sont - TypeScript n'a pas de castes de type qui changent réellement le type; elles sont simplement un moyen de supprimer les erreurs de vérification de type), la manière intellectuellement honnête d'aborder votre problème est d'écouter le messages d'erreur.

Dans votre cas, il y a 3 choses qui peuvent mal tourner:

  • document.getElementById("mycanvas")peut retourner null, car aucun nœud de cet identifiant n'est trouvé (il peut avoir été renommé, pas encore injecté dans le document, quelqu'un peut avoir essayé d'exécuter votre fonction dans un environnement sans accès au DOM)
  • document.getElementById("mycanvas") peut renvoyer une référence à un élément DOM, mais cet élément DOM n'est pas un HTMLCanvasElement
  • document.getElementById("mycanvas")a renvoyé un valide HTMLElement, c'est bien un HTMLCanvasElement, mais le CanvasRenderingContext2Dn'est pas pris en charge par le navigateur.

Au lieu de dire au compilateur de se taire (et éventuellement de vous retrouver dans une situation où un message d'erreur inutile comme Cannot read property 'getContext' of nullest émis), je vous recommande de prendre le contrôle des limites de votre application.

Assurez-vous que l'élément contient un 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;
}

Assurez-vous que le contexte de rendu est pris en charge par le navigateur

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

Usage:

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

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

Voir TypeScript Playground .

Karol Majewski
la source
1

J'ai eu le même problème, mais avec SVGSVGElement au lieu de HTMLCanvasElement. La conversion en SVGSVGElement a donné une erreur de compilation:

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

Impossible de convertir «HTMLElement» en «SVGSVGElement»: le
type «HTMLElement» ne contient pas la propriété «width» du type «SVGSVGElement».
Il manque la propriété «onmouseleave» du type «HTMLElement» de type «SVGSVGElement».

Si corrigé par le premier casting à 'any':

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

ou de cette façon (cela a le même effet)

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

Maintenant mySvg est fortement typé comme SVGSVGElement.

Edward
la source
0

C'est un vieux sujet ... peut-être mort en 2012, mais passionnant et nouveau pour VS Code et dactylographié.

J'ai dû faire ce qui suit pour que cela fonctionne dans VS Code avec les références de package suivantes.

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

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

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

Version dactylographiée:

{
    "@typescript-eslint/eslint-plugin": "^2.29.0",
    "@typescript-eslint/parser": "^2.29.0",
    "typescript": "^3.7.5"
}
un agent
la source
0

Vous devrez peut-être ajouter DOMà compilerOptions.libvotre tsconfig.json.

// 'tsconfig.json'
 {
  "compilerOptions": {
    "target": "ES2017",
    "module": "commonjs",
    "lib": [
      "es5",
      "DOM",
      "esnext"
    ]
  }
}
Barakat Turki
la source