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
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
// 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
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
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
// 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
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}`);
}
Saída:
// 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
// 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
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
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
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
// 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
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:
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
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();
Saída:
Save triggered
▶ Exemplo: Renderização type-safe com Canvas
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");
}
Saída:
No runtime output — demonstrates type-safe canvas rendering
❓ Perguntas Frequentes
P: O que é melhor: a asserção genérica
querySelectorou aas? R: A asserção genéricaquerySelectoré 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çãoasignora a verificação de nulo —document.getElementById("email") as HTMLInputElementSe o elemento não existir, o código subsequente irá travar em tempo de execução.
P: Qual é a diferença entre
event.targeteevent.currentTarget? R:event.targeté o elemento que realmente acionou o evento (que pode ser um elemento filho), enquantoevent.currentTargeté o elemento ao qual o ouvinte de eventos está vinculado (o elemento referido porthis). Esses dois podem diferir durante a propagação do evento. Geralmente, é mais seguro usarevent.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:
libdeve incluir"DOM". O Node.js não inclui tipos DOM por padrão — se o arquivotsconfigestiver configurado apenas com"ES2020"e não com"DOM", variáveis globais comodocumentewindowresultarã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
- Hierarquia de tipos do DOM:
HTMLElementé a classe base, e cada elemento possui sua própria subclasse específica (comoHTMLInputElement, etc.) - Uma consulta DOM retorna
| null— é preciso verificar se o valor é nulo antes de usá-lo; use oquerySelectorgenérico para obter um tipo específico - O tipo de evento é deduzido automaticamente a partir do nome do evento —
clické umMouseEventekeydowné umKeyboardEvent - evento.target deve ser qualificado com
instanceofpara acessar com segurança as propriedades específicas do elemento - O tipo genérico
CustomEventtransmite dados personalizados e pode estender a interfaceDocumentpara aprimorar a inferência de tipos. - Use
instanceofpara distinguir entre diferentes tipos de elementos ao trabalhar com formulários e acessar com segurança suas respectivas propriedades
📝 Exercícios
- Problema básico (Dificuldade ⭐): Usando TypeScript, selecione um elemento
inpute um elementobuttone, em seguida, adicione um ouvinte de evento de clique ao botão para que, quando ele for clicado, o valor doinputseja exibido. - 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 entradarequiredestão vazios e retorne o resultado da validação. - 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 umEventMapcontendo “user:login” (dados do tipoUser) e “cart:update” (dados do tipoCart) e verifique a segurança de tipos.