TypeScript: TypeScript and DOM Manipulation

Last updated: 2026-08-26

In browser development, DOM manipulation is the most common scenario—TypeScript includes comprehensive built-in DOM type declarations, providing type safety for element selection, event handling, and form manipulation.

1. The DOM Type System

(1) HTMLElement Inheritance Hierarchy

TEXT 📖 Display only
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) Common DOM Types

Type Corresponding Element Unique Properties
HTMLElement All HTML elements className, style, innerHTML
HTMLInputElement <input> value, type, checked, placeholder
HTMLButtonElement <button> disabled, type
HTMLSelectElement <select> value, selectedIndex, options
HTMLTextAreaElement <textarea> value, rows, cols
HTMLCanvasElement <canvas> getContext(), width, height
HTMLFormElement <form> elements, submit(), reset()
HTMLImageElement <img> src, alt, width, height

(3) Types of DOM Queries

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. DOM Queries and Type Narrowing

(1) Null Checks

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) Narrowing the Scope of Specific Element Types

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 generic

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
}

▶ Example: Type-Safe Form Data Collection

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}`);
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
Email:user@example.com
Remember Me:true


3. Event Types

(1) Common Event Types

Event Type Trigger Scenario Specific Attributes
MouseEvent Mouse click/movement clientX, clientY, button
KeyboardEvent Keyboard input key, code, ctrlKey, shiftKey
InputEvent Input Changes data, inputType
FocusEvent Focus change relatedTarget
SubmitEvent Form Submission submitter
ChangeEvent Value Change target
ClipboardEvent Clipboard clipboardData
DragEvent Drag dataTransfer
WheelEvent roller deltaY, deltaX
TouchEvent Touch keys

(2) Types of Event Listeners

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) Narrowing the Types of Event Goals

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. Type Safety in Form Operations

(1) Iterating Through Form Elements

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) Form Validation

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. Custom Events and Data Passing

(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) Listening for Custom Events

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) Extend the Document interface

If TypeScript does not automatically infer the type of a custom event, you can extend the Document interface:

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

▶ Example: Keyboard Event Type Handling

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();
▶ Try it Yourself

Output:

TEXT 📖 Display only
Save triggered

▶ Example: Type-Safe Canvas Rendering

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");
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
No runtime output — demonstrates type-safe canvas rendering

❓ FAQ

Q Which is better, the generic querySelector or the as assertion?
A The generic querySelector is better—document.querySelector<HTMLInputElement>("#email") It specifies the type at the time of the query, and when used with a null check, it ensures safe usage. The as assertion skips the null check—document.getElementById("email") as HTMLInputElement If the element does not exist, the subsequent code will crash at runtime.
Q What is the difference between event.target and event.currentTarget?
A event.target is the element that actually triggered the event (which may be a child element), while event.currentTarget is the element to which the event listener is bound (the element referred to by this). These two may differ during event bubbling. It’s generally safer to use event.currentTarget—it’s the element to which you’ve attached the event listener.
Q Do DOM operations have a significant impact on performance?
A Type annotations have zero impact on performance (they are erased at compile time). The performance of DOM operations themselves depends on the code logic—frequent DOM reads and writes can trigger reflows and repaints. TypeScript’s type system helps you write safer DOM code, but it does not affect the runtime performance of DOM operations.
Q Are there DOM types in non-browser environments?
A lib must include "DOM". Node.js does not include DOM types by default—if the tsconfig file is configured with only "ES2020" and not "DOM", global variables such as document and window will result in errors. DOM types are not required for Node.js backend projects, but they must be included in frontend projects.

📖 Summary

📝 Exercises

  1. Basic Problem (Difficulty ⭐): Using TypeScript, select an input element and a button element, then add a click event listener to the button so that when it is clicked, the value of the input is printed.
  2. Advanced Problem (Difficulty ⭐⭐): Write a function validateForm(form: HTMLFormElement): { valid: boolean; errors: Record<string, string> } that iterates through form elements, checks whether required input fields are empty, and returns the validation result.
  3. Challenge (Difficulty: ⭐⭐⭐): Implement a type-safe event bus—EventBus.on<K extends keyof EventMap>(event: K, handler: (data: EventMap[K]) => void), EventBus.emit<K>(event: K, data: EventMap[K]). Define an EventMap containing "user:login" (data of type User) and "cart:update" (data of type Cart), and verify type safety.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏