TypeScript: problemas com o sistema de tipos

101

Estou apenas testando o texto digitado no VisualStudio 2012 e tenho um problema com seu sistema de tipos. Meu site html tem uma tag de tela com o id "mycanvas". Estou tentando desenhar um retângulo nesta tela. Aqui está o código

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

Infelizmente VisualStudio reclama que

a propriedade 'getContext' não existe no valor do tipo 'HTMLElement'

Ele marca a segunda linha como um erro. Achei que fosse apenas um aviso, mas o código não compila. VisualStudio diz que

houve erros de construção. Você gostaria de continuar e executar a última compilação bem-sucedida?

Não gostei nem um pouco desse erro. Por que não há invocação de método dinâmico? Afinal, o método getContext definitivamente existe no meu elemento canvas. Porém achei que esse problema seria fácil de resolver. Acabei de adicionar uma anotação de tipo para tela:

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

Mas o sistema de tipos ainda não estava satisfeito. Esta é a nova mensagem de erro, desta vez na primeira linha:

Não é possível converter 'HTMLElement' em 'HTMLCanvasElement': o tipo 'HTMLElement' está sem a propriedade 'toDataURL' do tipo 'HTMLCanvasElement'

Bem, estou totalmente interessado em digitação estática, mas isso torna a linguagem inutilizável. O que o sistema de tipos quer que eu faça?

ATUALIZAR:

De fato, o typescript não tem suporte para invocação dinâmica e meu problema pode ser resolvido com typecasts. Minha pergunta é basicamente uma duplicata deste TypeScript: casting HTMLElement

lhk
fonte

Respostas:

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

ou usando pesquisa dinâmica com o anytipo (sem verificação de tipo):

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

Você pode olhar para os diferentes tipos das lib.d.ts .

Markus Jarderot
fonte
9
Vale a pena mencionar que é melhor usar em CanvasRenderingContext2Dvez de anydigitar para o contexto da tela.
Ivan Kochurkin,
3
Desde TypeScript 1.8, ele reconhecerá o argumento de string constante "2d"e .getContext("2d")retornará com o tipo CanvasRenderingContext2D. Você não precisa lançá-lo explicitamente.
Markus Jarderot
13
const canvas =  document.getElementById('stage') as HTMLCanvasElement;
Halil Kocaerkek
fonte
7
Explique por que esse código ajuda a resolver o problema do OP em vez de apenas uma resposta de código. O que seu código faz de diferente, como isso ajuda?
Você copia / cola e funciona. O que há para explicar aqui? Se não funcionar para você, você deve explicar seu problema e perguntar mais especificamente.
lenooh
6

Enquanto outras respostas promovem afirmações de tipo (é isso que elas são - o TypeScript não tem conversão de tipo que realmente muda o tipo; elas são meramente uma forma de suprimir erros de verificação de tipo), a maneira intelectualmente honesta de abordar seu problema é ouvir o mensagens de erro.

No seu caso, existem três coisas que podem dar errado:

  • document.getElementById("mycanvas")pode retornar null, porque nenhum nó desse id foi encontrado (ele pode ter sido renomeado, não foi injetado no documento ainda, alguém pode ter tentado executar sua função em um ambiente sem acesso ao DOM)
  • document.getElementById("mycanvas") pode retornar uma referência a um elemento DOM, mas este elemento DOM não é um HTMLCanvasElement
  • document.getElementById("mycanvas")retornou um válido HTMLElement, é de fato um HTMLCanvasElement, mas CanvasRenderingContext2Dnão é compatível com o navegador.

Em vez de dizer ao compilador para calar a boca (e possivelmente se encontrar em uma situação em que uma mensagem de erro inútil como Cannot read property 'getContext' of nullseja lançada), recomendo assumir o controle sobre os limites do aplicativo.

Certifique-se de que o elemento contém um 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;
}

Certifique-se de que o contexto de renderização é compatível com o navegador

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

Uso:

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

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

Consulte Playground TypeScript .

Karol Majewski
fonte
1

Eu tive o mesmo problema, mas com SVGSVGElement em vez de HTMLCanvasElement. A conversão para SVGSVGElement gerou um erro de tempo de compilação:

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

Não é possível converter 'HTMLElement' em 'SVGSVGElement': o
tipo 'HTMLElement' está sem a propriedade 'width' do tipo 'SVGSVGElement'.
O tipo 'SVGSVGElement' não contém a propriedade 'onmouseleave' do tipo 'HTMLElement'.

Se corrigido primeiro lançando para 'qualquer':

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

ou desta forma (tem o efeito idêntico)

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

Agora mySvg é fortemente tipado como SVGSVGElement.

Edward
fonte
0

Este é um tópico antigo ... talvez morto para 2012, mas excitante e novo para VS Code e datilografado.

Eu tive que fazer o seguinte para fazer isso funcionar no VS Code com as seguintes referências de pacote.

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

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

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

Versão datilografada:

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

um agente
fonte
0

Você pode ter que adicionar DOMa compilerOptions.libsua tsconfig.json.

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