TypeScript: TypeScript e manipulação do DOM

Última atualização: 2026-08-26

No desenvolvimento de navegadores, a manipulação do DOM é o cenário mais comum — o TypeScript inclui declarações de tipos de DOM integradas e abrangentes, proporcionando segurança de tipos para a seleção de elementos, o tratamento de eventos e a manipulação de formulários.

1. O sistema de tipos do DOM

(1) Hierarquia de herança do HTMLElement

TEXT 📖 Somente leitura
EventTarget
  └─ Node
       └─ Element
            └─ HTMLElement
                 ├─ HTMLInputElement    (input Element)
                 ├─ HTMLButtonElement   (button Element)
                 ├─ HTMLDivElement      (div Element)
                 ├─ HTMLSpanElement     (span Element)
                 ├─ HTMLAnchorElement   (a Element)
                 ├─ HTMLCanvasElement   (canvas Element)
                 ├─ HTMLSelectElement   (select Element)
                 ├─ HTMLTextAreaElement (textarea Element)
                 └─ ... More Subclasses

(2) Tipos comuns de DOM

Tipo Elemento correspondente Propriedades exclusivas
HTMLElement Todos os elementos HTML className, style, innerHTML
HTMLInputElement <input> valor, tipo, verificado, espaço reservado
HTMLButtonElement <button> desativado, tipo
HTMLSelectElement <select> valor, índiceSelecionado, opções
HTMLTextAreaElement <textarea> valor, linhas, colunas
HTMLCanvasElement <canvas> getContext(), largura, altura
HTMLFormElement <form> elementos, submit(), reset()
HTMLImageElement <img> src, alt, width, height

(3) Tipos de consultas ao DOM

TYPESCRIPT
// getElementById Back HTMLElement | null
let el = document.getElementById("app");

// querySelector Back Element | null
let first = document.querySelector(".item");

// querySelectorAll Back NodeListOf<Element>
let all = document.querySelectorAll(".item");


2. Consultas DOM e restrição de tipos

(1) Verificações de valores nulos

TYPESCRIPT
let el = document.getElementById("app");

// ❌ Use as is——It could be null
// el.innerHTML = "Hello";

// ✅ Method 1:if Inspection
if (el) {
  el.innerHTML = "Hello";
}

// ✅ Method 2:Non-empty assertion(When an element is found)
el!.innerHTML = "Hello";

// ✅ Method 3:Return Early
if (!el) return;
el.innerHTML = "Hello";  // After that el Definitely not null

(2) Restrição do escopo de tipos específicos de elementos

TYPESCRIPT
let input = document.getElementById("email");

// input The type is HTMLElement | null——Cannot access value Properties
// input.value  ❌

// ✅ Method 1:instanceof narrow
if (input instanceof HTMLInputElement) {
  console.log(input.value);  // ✅ HTMLInputElement has value
}

// ✅ Method 2:Type Assertion
let emailInput = document.getElementById("email") as HTMLInputElement;
console.log(emailInput.value);  // ✅

// ✅ Method 3:Generic Queries(Top Recommendations)
let emailInput2 = document.querySelector<HTMLInputElement>("#email");
if (emailInput2) {
  console.log(emailInput2.value);  // ✅ Automatically inferred as HTMLInputElement
}

(3) querySelector genérico

TYPESCRIPT
// Generic parameters specify the expected element type
let input = document.querySelector<HTMLInputElement>("input[type=email]");
let button = document.querySelector<HTMLButtonElement>("#submit");
let canvas = document.querySelector<HTMLCanvasElement>("canvas");
let form = document.querySelector<HTMLFormElement>("form");

if (input) {
  console.log(input.value);       // ✅ HTMLInputElement.value
}
if (canvas) {
  let ctx = canvas.getContext("2d");  // ✅ CanvasRenderingContext2D | null
}

▶ Exemplo: Coleta de dados de formulário com segurança de tipos

TYPESCRIPT
interface LoginForm {
  email: string;
  password: string;
  remember: boolean;
}

function getFormData(formEl: HTMLFormElement): LoginForm {
  let email = formEl.querySelector<HTMLInputElement>('input[name="email"]');
  let password = formEl.querySelector<HTMLInputElement>('input[name="password"]');
  let remember = formEl.querySelector<HTMLInputElement>('input[name="remember"]');

  return {
    email: email?.value ?? "",
    password: password?.value ?? "",
    remember: remember?.checked ?? false
  };
}

// Usage
let form = document.querySelector<HTMLFormElement>("#login-form");
if (form) {
  let data = getFormData(form);
  console.log(`Email:${data.email}`);
  console.log(`Remember Me:${data.remember}`);
}
▶ Experimente

Saída:

TEXT 📖 Somente leitura
// Executed successfully


3. Tipos de eventos

(1) Tipos comuns de eventos

Tipo de evento Cenário de acionamento Atributos específicos
MouseEvent Clique/movimento do mouse clientX, clientY, botão
KeyboardEvent Entrada do teclado key, code, ctrlKey, shiftKey
InputEvent Alterações na entrada dados, tipo de entrada
FocusEvent Mudança de foco relatedTarget
SubmitEvent Envio do formulário remetente
ChangeEvent Variação do valor meta
ClipboardEvent Área de trabalho clipboardData
DragEvent Arrastar transferência de dados
WheelEvent rolo deltaY, deltaX
TouchEvent Tátil teclas

(2) Tipos de ouvintes de eventos

TYPESCRIPT
// addEventListener The type is automatically inferred from the event name
const button = document.querySelector("button");

button?.addEventListener("click", (event) => {
  // event Automatically inferred as MouseEvent
  console.log(event.clientX, event.clientY);  // ✅
});

document.addEventListener("keydown", (event) => {
  // event Automatically inferred as KeyboardEvent
  console.log(event.key);     // ✅
  console.log(event.ctrlKey); // ✅
  if (event.key === "Enter" && event.ctrlKey) {
    console.log("Ctrl+Enter Press");
  }
});

(3) Restringindo os tipos de metas dos eventos

TYPESCRIPT
function handleInput(event: Event) {
  let target = event.target;

  // target The type is EventTarget | null——Needs to be narrowed
  if (target instanceof HTMLInputElement) {
    console.log(target.value);    // ✅ HTMLInputElement.value
    console.log(target.type);     // ✅ HTMLInputElement.type
  }
}

// Abbreviation——Generic Event Handling
function onInput(event: InputEvent) {
  let target = event.target as HTMLInputElement;
  console.log(target.value);  // ✅
}


4. Segurança de tipos nas operações com formulários

(1) Percorrendo os elementos de um formulário

TYPESCRIPT
function collectForm(form: HTMLFormElement): Record<string, string | boolean> {
  let data: Record<string, string | boolean> = {};

  for (let element of Array.from(form.elements)) {
    if (element instanceof HTMLInputElement) {
      if (element.type === "checkbox") {
        data[element.name] = element.checked;
      } else {
        data[element.name] = element.value;
      }
    } else if (element instanceof HTMLSelectElement) {
      data[element.name] = element.value;
    } else if (element instanceof HTMLTextAreaElement) {
      data[element.name] = element.value;
    }
  }

  return data;
}

(2) Validação de formulário

TYPESCRIPT
interface ValidationRule {
  required?: boolean;
  minLength?: number;
  maxLength?: number;
  pattern?: RegExp;
  message: string;
}

function validateInput(
  input: HTMLInputElement,
  rules: ValidationRule[]
): string | null {
  let value = input.value;

  for (let rule of rules) {
    if (rule.required && !value.trim()) {
      return rule.message;
    }
    if (rule.minLength && value.length < rule.minLength) {
      return rule.message;
    }
    if (rule.maxLength && value.length > rule.maxLength) {
      return rule.message;
    }
    if (rule.pattern && !rule.pattern.test(value)) {
      return rule.message;
    }
  }

  return null;  // Verification Passed
}

// Usage
let email = document.querySelector<HTMLInputElement>('input[name="email"]');
if (email) {
  let error = validateInput(email, [
    { required: true, message: "The email address cannot be left blank." },
    { pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, message: "The email address format is incorrect." }
  ]);
  if (error) {
    console.log(error);
  }
}


5. Eventos personalizados e passagem de dados

(1) CustomEvent

TYPESCRIPT
// Create a Custom Event and Pass Data
interface CartItemAddedDetail {
  itemId: number;
  quantity: number;
  price: number;
}

let event = new CustomEvent<CartItemAddedDetail>("cart:itemadded", {
  detail: { itemId: 1, quantity: 2, price: 99.9 },
  bubbles: true
});

document.dispatchEvent(event);

(2) Detecção de eventos personalizados

TYPESCRIPT
document.addEventListener("cart:itemadded", (event) => {
  // event Type automatically inferred as CustomEvent<CartItemAddedDetail>
  console.log(event.detail.itemId);     // ✅ number
  console.log(event.detail.quantity);   // ✅ number
  console.log(event.detail.price);      // ✅ number
});

(3) Estender a interface Document

Se o TypeScript não inferir automaticamente o tipo de um evento personalizado, você pode estender a interface Document:

TYPESCRIPT
interface CartEventMap {
  "cart:itemadded": CustomEvent<CartItemAddedDetail>;
  "cart:itemremoved": CustomEvent<{ itemId: number }>;
}

declare global {
  interface Document {
    addEventListener<K extends keyof CartEventMap>(
      type: K,
      listener: (this: Document, ev: CartEventMap[K]) => void
    ): void;
  }
}


▶ Exemplo: Tratamento de tipos de eventos de teclado

TYPESCRIPT
function setupKeyboardShortcuts(): void {
  document.addEventListener("keydown", (event: KeyboardEvent) => {
    if (event.ctrlKey && event.key === "s") {
      event.preventDefault();
      console.log("Save triggered");
    }
    if (event.key === "Escape") {
      let modal = document.querySelector<HTMLDialogElement>("dialog[open]");
      modal?.close();
    }
  });
}

setupKeyboardShortcuts();
▶ Experimente

Saída:

TEXT 📖 Somente leitura
Save triggered

▶ Exemplo: Renderização type-safe com Canvas

TYPESCRIPT
function drawCircle(
  canvas: HTMLCanvasElement,
  x: number, y: number, radius: number, color: string
): void {
  let ctx = canvas.getContext("2d");
  if (!ctx) return;

  ctx.fillStyle = color;
  ctx.beginPath();
  ctx.arc(x, y, radius, 0, Math.PI * 2);
  ctx.fill();
}

let canvas = document.querySelector<HTMLCanvasElement>("#myCanvas");
if (canvas) {
  drawCircle(canvas, 100, 100, 50, "blue");
}
▶ Experimente

Saída:

TEXT 📖 Somente leitura
No runtime output — demonstrates type-safe canvas rendering

❓ Perguntas Frequentes

P: O que é melhor: a asserção genérica querySelector ou a as? R: A asserção genérica querySelector é melhor — document.querySelector<HTMLInputElement>("#email") Ela especifica o tipo no momento da consulta e, quando usada com uma verificação de nulo, garante um uso seguro. A asserção as ignora a verificação de nulo — document.getElementById("email") as HTMLInputElement Se o elemento não existir, o código subsequente irá travar em tempo de execução.

P: Qual é a diferença entre event.target e event.currentTarget? R: event.target é o elemento que realmente acionou o evento (que pode ser um elemento filho), enquanto event.currentTarget é o elemento ao qual o ouvinte de eventos está vinculado (o elemento referido por this). Esses dois podem diferir durante a propagação do evento. Geralmente, é mais seguro usar event.currentTarget — é o elemento ao qual você vinculou o ouvinte de eventos.

P: As operações DOM têm um impacto significativo no desempenho? R: As anotações de tipo não têm nenhum impacto no desempenho (elas são removidas na compilação). O desempenho das próprias operações DOM depende da lógica do código — leituras e gravações frequentes no DOM podem desencadear reflows e repaints. O sistema de tipos do TypeScript ajuda a escrever código DOM mais seguro, mas não afeta o desempenho em tempo de execução das operações DOM.

P: Existem tipos DOM em ambientes que não sejam navegadores? R: lib deve incluir "DOM". O Node.js não inclui tipos DOM por padrão — se o arquivo tsconfig estiver configurado apenas com "ES2020" e não com "DOM", variáveis globais como document e window resultarão em erros. Os tipos DOM não são necessários para projetos de back-end do Node.js, mas devem ser incluídos em projetos de front-end.

📖 Resumo

📝 Exercícios

  1. Problema básico (Dificuldade ⭐): Usando TypeScript, selecione um elemento input e um elemento button e, em seguida, adicione um ouvinte de evento de clique ao botão para que, quando ele for clicado, o valor do input seja exibido.
  2. Problema avançado (Dificuldade ⭐⭐): Escreva uma função validateForm(form: HTMLFormElement): { valid: boolean; errors: Record<string, string> } que percorra os elementos do formulário, verifique se os campos de entrada required estão vazios e retorne o resultado da validação.
  3. Desafio (Dificuldade: ⭐⭐⭐): Implemente um barramento de eventos com segurança de tipos — EventBus.on<K extends keyof EventMap>(event: K, handler: (data: EventMap[K]) => void), EventBus.emit<K>(event: K, data: EventMap[K]). Defina um EventMap contendo “user:login” (dados do tipo User) e “cart:update” (dados do tipo Cart) e verifique a segurança de tipos.
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%