TypeScript 65 🔷 Typing Event Handlers and DOM
The DOM is where the browser’s types live. Every element, every event, every property of the window and document has a declaration in the DOM library that TypeScript loads by default. When you write document.getElementById('app'), the return type is HTMLElement | null, and the compiler forces you to handle the null. When you write element.addEventListener('click', handler), the handler’s parameter is typed as MouseEvent, and the event.target is EventTarget | null. The DOM’s types are precise, and the precision is what makes the browser code type-safe. But the types have subtleties: the EventTarget and the HTMLElement are different, the event.target is not the event.currentTarget, the generic addEventListener narrows the event type from the event name, and the query selectors return the specific element types. This chapter covers the DOM’s type system: the element types, the event types, the event handler typing, the generic addEventListener, the type guards for the elements and the events, and the patterns that make the browser code safe.
Key point: The DOM’s types are declared in the lib.dom.d.ts file, which TypeScript loads when the lib option includes the dom. The HTMLElement is the base for the HTML elements, and the specific types — HTMLInputElement, HTMLButtonElement, HTMLDivElement — extend it. The Event is the base for the events, and the specific types — MouseEvent, KeyboardEvent, InputEvent — extend it. The addEventListener is generic over the event name, and the handler’s parameter is narrowed from the event name’s literal type. The event.target is the EventTarget | null, and the event.currentTarget is the element the listener is attached to. The query selectors return the HTMLElement | null or the generic type, and the type guard is the way to narrow.
The DOM’s type hierarchy
The DOM’s types are organized in a hierarchy. The Node is the base for the tree nodes, the Element extends the Node, the HTMLElement extends the Element, and the specific elements extend the HTMLElement.
EventTarget
└── Node
└── Element
└── HTMLElement
├── HTMLInputElement
├── HTMLButtonElement
├── HTMLDivElement
└── ...
Event
├── UIEvent
│ ├── MouseEvent
│ └── KeyboardEvent
├── InputEvent
├── FocusEvent
└── ...
Why the hierarchy matters. The HTMLElement has the style, the className, and the click method. The HTMLInputElement adds the value, the checked, and the files. The HTMLButtonElement adds the disabled and the type. The specific element’s properties are available only on the specific type, and the narrowing is the way to access them.
Why the Element is not the HTMLElement. The Element is the base for the HTML, the SVG, and the MathML elements. The SVGElement is the Element but not the HTMLElement. The HTMLElement is the HTML-specific, and the Element is the general. The distinction matters for the properties that are HTML-specific.
Why the Node is the general. The Node is the base for the Element, the Text, the Comment, and the Document. The Node has the childNodes, the parentNode, and the nodeType. The traversal methods are on the Node, and the element-specific methods are on the Element.
Why the EventTarget is the base for the events. The EventTarget is the interface for the objects that can have the listeners. The Node, the Window, and the XMLHttpRequest are the EventTargets. The addEventListener is on the EventTarget, and the element-specific listeners are on the element’s type.
Why the specific types have the specific properties. The HTMLInputElement.value is a string, and the HTMLInputElement.checked is a boolean. The properties are declared on the specific type, and the compiler knows them. The narrowing from the HTMLElement to the HTMLInputElement is the way to access them.
Why the DOM’s types are the global. The lib.dom.d.ts declares the HTMLElement, the Event, and the other DOM types in the global scope. The types are available without the import, and the document and the window are the global values. The types are the browser’s, and the compiler loads them.
Why the DOM’s types are separate from the TypeScript’s types. The DOM is the browser’s API, and the types are maintained by the TypeScript team in the
lib.dom.d.ts. The Node.js has its own types in the@types/node, and the two are separate. The project that targets the browser includes thedomlib, and the project that targets the Node excludes it.
The element types
The HTMLElement is the base for the HTML elements, and the specific types add the specific properties. The query selectors and the createElement return the specific types when the compiler can infer them.
The document.getElementById. The method returns the HTMLElement | null, and the null is the required check.
const el = document.getElementById('app');
// el is HTMLElement | null
if (el) {
el.textContent = 'Hello'; // el is HTMLElement
}
The el is the HTMLElement | null, and the if (el) narrows it to the HTMLElement. The null check is the requirement, and the compiler enforces it.
Why the null is the required check. The getElementById returns the null when the element does not exist. The compiler forces the check, and the runtime error is prevented.
The document.querySelector. The method returns the Element | null, and the type is the generic when the selector is the type parameter.
const input = document.querySelector<HTMLInputElement>('input[name="email"]');
// input is HTMLInputElement | null
if (input) {
input.value = 'user@example.com';
}
The <HTMLInputElement> is the type parameter, and the querySelector returns the HTMLInputElement | null. The type parameter is the assertion, and the compiler trusts it.
Why the type parameter is the assertion. The querySelector cannot know the element type from the selector string. The type parameter is the developer’s claim, and the compiler trusts it. The wrong type parameter is the runtime error, and the discipline is to use the correct one.
The document.querySelectorAll. The method returns the NodeListOf<Element>, and the generic narrows it.
const inputs = document.querySelectorAll<HTMLInputElement>('input');
inputs.forEach((input) => {
input.value = '';
});
The NodeListOf<HTMLInputElement> is the collection, and the forEach iterates. The NodeListOf is the array-like, and it has the forEach in the modern browsers.
The document.createElement. The method returns the specific type from the tag name.
const div = document.createElement('div');
// div is HTMLDivElement
const input = document.createElement('input');
// input is HTMLInputElement
The tag name’s literal type determines the return type, and the compiler infers the specific type from the map. The 'div' is the HTMLDivElement, and the 'input' is the HTMLInputElement.
Why the createElement is the specific type. The DOM’s types have the HTMLElementTagNameMap interface that maps the tag names to the types. The createElement uses the map, and the return type is the specific.
const element = document.createElement('custom-element');
// element is HTMLElement (the default for the unknown tag)
The unknown tag returns the HTMLElement, which is the default. The known tags return the specific types.
Why the HTMLElementTagNameMap can be augmented. The custom elements can be added to the map, and the createElement returns the custom type.
declare global {
interface HTMLElementTagNameMap {
'my-element': MyElement;
}
}
The augmentation adds the custom element to the map, and the createElement('my-element') returns the MyElement. The pattern is the custom elements’ typing.
The event types
The Event is the base for the events, and the specific types add the specific properties. The MouseEvent has the clientX and the clientY, the KeyboardEvent has the key and the code, and the InputEvent has the data.
The Event base. The Event has the type, the target, the currentTarget, the preventDefault, and the stopPropagation.
function handler(event: Event): void {
console.log(event.type);
event.preventDefault();
}
The Event is the base, and the type is the string. The preventDefault and the stopPropagation are the methods.
The MouseEvent. The MouseEvent extends the UIEvent and adds the mouse-specific properties.
function onClick(event: MouseEvent): void {
console.log(event.clientX, event.clientY);
console.log(event.button);
}
The clientX and the clientY are the coordinates, and the button is the mouse button. The properties are on the MouseEvent, and the compiler knows them.
The KeyboardEvent. The KeyboardEvent extends the UIEvent and adds the keyboard-specific properties.
function onKeyDown(event: KeyboardEvent): void {
console.log(event.key, event.code);
if (event.key === 'Enter') {
submit();
}
}
The key is the character, and the code is the physical key. The key is the layout-dependent, and the code is the layout-independent.
The InputEvent. The InputEvent extends the UIEvent and adds the input-specific properties.
function onInput(event: InputEvent): void {
console.log(event.data);
console.log(event.inputType);
}
The data is the inserted text, and the inputType is the insertion type. The InputEvent is the modern event for the input.
The Event to the specific’s narrowing. The handler’s parameter is the specific type when the addEventListener is used with the specific event name. The narrowing is automatic.
element.addEventListener('click', (event) => {
// event is MouseEvent
console.log(event.clientX);
});
The 'click' narrows the event to the MouseEvent, and the clientX is available. The generic addEventListener is the mechanism.
Why the specific event’s properties are available. The addEventListener‘s overloads map the event names to the event types. The 'click' is the MouseEvent, the 'keydown' is the KeyboardEvent, and the 'input' is the Event (or the InputEvent in the modern). The map is the HTMLElementEventMap, and the overloads use it.
Why the event’s properties should be accessed on the specific type. The Event does not have the clientX, and the MouseEvent does. The handler’s parameter must be the specific type to access the properties, and the addEventListener provides it.
The addEventListener typing
The addEventListener is generic over the event name. The event name’s literal type determines the event’s type, and the handler’s parameter is narrowed.
element.addEventListener('click', (event) => {
// event is MouseEvent
});
element.addEventListener('keydown', (event) => {
// event is KeyboardEvent
});
element.addEventListener('input', (event) => {
// event is Event
});
The event name’s literal type is the key, and the event’s type is the value. The addEventListener‘s overloads use the map, and the handler is typed.
Why the literal type matters. The 'click' is the literal, and the MouseEvent is the mapped. The string would not narrow, and the handler’s parameter would be the Event. The literal is the narrowing, and the specific is the result.
Why the addEventListener is on the EventTarget. The EventTarget has the addEventListener, and the element extends it. The method is available on the element, the window, and the other event targets.
The overloads. The addEventListener has the multiple overloads.
addEventListener<K extends keyof HTMLElementEventMap>(
type: K,
listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => void,
options?: boolean | AddEventListenerOptions,
): void;
addEventListener(
type: string,
listener: EventListenerOrEventListenerObject,
options?: boolean | AddEventListenerOptions,
): void;
The first overload is the specific, and the second is the general. The specific is the narrowing, and the general is the fallback.
Why the first overload is the specific. The K extends keyof HTMLElementEventMap is the event name’s literal, and the HTMLElementEventMap[K] is the event’s type. The handler’s parameter is the event’s type, and the this is the HTMLElement.
Why the second overload is the general. The string is the general event name, and the EventListenerOrEventListenerObject is the general handler. The general is the fallback for the event names that are not in the map.
The this parameter. The handler’s this is the element the listener is attached to.
button.addEventListener('click', function (event) {
// this is HTMLButtonElement
this.disabled = true;
});
The function is the regular, and the this is the HTMLButtonElement. The arrow function’s this is the lexical, and the this in the handler is the element only with the regular function.
Why the this is the element. The addEventListener calls the handler with the element as the this. The regular function’s this is the element, and the arrow’s this is the enclosing scope. The two are the difference, and the choice matters.
The removal. The removeEventListener requires the same function reference, and the arrow function’s reference is the variable.
const handler = (event: MouseEvent): void => { ... };
element.addEventListener('click', handler);
element.removeEventListener('click', handler);
The handler is the variable, and the same reference is used for the removal. The inline arrow function cannot be removed, and the variable is the pattern.
The type guards
The DOM’s types require the narrowing for the elements and the events. The type guards are the way to narrow.
The element’s type guard. The instanceof narrows the element to the specific type.
const el = document.getElementById('input');
if (el instanceof HTMLInputElement) {
el.value = 'text'; // el is HTMLInputElement
}
The instanceof HTMLInputElement narrows the HTMLElement | null to the HTMLInputElement. The value is available, and the compiler knows.
Why the instanceof is the runtime check. The instanceof checks the prototype chain at runtime, and the type guard narrows the type at compile time. The two are the same, and the guard is the safety.
The custom type guard. The custom type guard is the function with the is predicate.
function isInputElement(el: Element): el is HTMLInputElement {
return el.tagName === 'INPUT';
}
const el = document.querySelector('input');
if (el && isInputElement(el)) {
el.value = 'text';
}
The isInputElement is the type predicate, and the el is HTMLInputElement is the narrowing. The custom guard is the reusable check.
Why the custom guard is the reusable. The instanceof is the inline, and the custom guard is the function. The function can be used in the multiple places, and the logic is the single source.
The event’s target narrowing. The event.target is the EventTarget | null, and the narrowing is required.
element.addEventListener('click', (event) => {
const target = event.target;
if (target instanceof HTMLButtonElement) {
console.log(target.disabled);
}
});
The event.target is the EventTarget | null, and the instanceof HTMLButtonElement narrows it. The disabled is available.
Why the target is the EventTarget | null. The event’s target is the element that dispatched the event, and the EventTarget is the base. The null is the possibility, and the check is the requirement.
Why the currentTarget is the element. The event.currentTarget is the element the listener is attached to, and the type is the element’s type.
button.addEventListener('click', (event) => {
// event.currentTarget is HTMLButtonElement
console.log(event.currentTarget.disabled);
});
The currentTarget is the HTMLButtonElement, and the disabled is available. The target is the EventTarget | null, and the two are different.
Why the currentTarget is the listener’s element. The currentTarget is the element the listener is attached to, and the target is the element that dispatched the event. The two are the same when the event is dispatched on the element, and different when the event bubbles from the child.
Why the currentTarget can be the null. The currentTarget is the null outside the event’s dispatch, and the type is the EventTarget | null in the general. The addEventListener‘s specific overload narrows the this to the element, and the handler’s this is the element.
The closest method. The closest method finds the ancestor that matches the selector, and the type is the generic.
element.addEventListener('click', (event) => {
const button = (event.target as Element).closest<HTMLButtonElement>('button');
if (button) {
console.log(button.disabled);
}
});
The closest<HTMLButtonElement> is the generic, and the return is the HTMLButtonElement | null. The type parameter is the assertion, and the check is the requirement.
Complete Example Session
// ============================================
// PART 1: THE GET ELEMENT
// ============================================
const el = document.getElementById('app');
// el is HTMLElement | null
if (el) {
el.textContent = 'Hello';
}
// ============================================
// PART 2: THE QUERY SELECTOR
// ============================================
const input = document.querySelector<HTMLInputElement>('input[name="email"]');
// input is HTMLInputElement | null
if (input) {
input.value = 'user@example.com';
input.focus();
}
// ============================================
// PART 3: THE CREATE ELEMENT
// ============================================
const div = document.createElement('div');
// div is HTMLDivElement
const button = document.createElement('button');
// button is HTMLButtonElement
button.disabled = true;
// ============================================
// PART 4: THE ADD EVENT LISTENER
// ============================================
const element = document.getElementById('button');
if (element) {
element.addEventListener('click', (event) => {
// event is MouseEvent
console.log(event.clientX, event.clientY);
});
element.addEventListener('keydown', (event) => {
// event is KeyboardEvent
if (event.key === 'Enter') {
console.log('Enter pressed');
}
});
}
// ============================================
// PART 5: THE THIS IN THE HANDLER
// ============================================
const btn = document.querySelector('button');
if (btn) {
btn.addEventListener('click', function (event) {
// this is HTMLButtonElement
this.disabled = true;
});
}
// ============================================
// PART 6: THE REMOVAL
// ============================================
const handler = (event: MouseEvent): void => {
console.log('clicked');
};
const target = document.getElementById('button');
if (target) {
target.addEventListener('click', handler);
target.removeEventListener('click', handler);
}
// ============================================
// PART 7: THE ELEMENT TYPE GUARD
// ============================================
const maybeInput = document.getElementById('input');
if (maybeInput instanceof HTMLInputElement) {
maybeInput.value = 'text';
}
// ============================================
// PART 8: THE CUSTOM TYPE GUARD
// ============================================
function isInputElement(el: Element): el is HTMLInputElement {
return el.tagName === 'INPUT';
}
const maybeEl = document.querySelector('input');
if (maybeEl && isInputElement(maybeEl)) {
maybeEl.value = 'text';
}
// ============================================
// PART 9: THE TARGET NARROWING
// ============================================
const container = document.getElementById('container');
if (container) {
container.addEventListener('click', (event) => {
const target = event.target;
if (target instanceof HTMLButtonElement) {
console.log(target.disabled);
}
});
}
// ============================================
// PART 10: WHAT NOT TO DO
// ============================================
// Don't forget the null check
const bad = document.getElementById('app');
// bad.textContent = 'Hello'; // ❌ bad is possibly null
// Don't use the wrong type parameter
const wrong = document.querySelector<HTMLInputElement>('div'); // the assertion is wrong
// Don't use the arrow function for the this
element?.addEventListener('click', () => {
// this is not the element
});
// Don't use the Event for the specific properties
function onClick(event: Event): void {
// event.clientX // ❌ Event has no clientX
}
// Don't cast the event target without the check
const target2 = (event.target as HTMLButtonElement); // the cast is the risk
// Don't forget the removeEventListener's same reference
element?.addEventListener('click', () => {});
element?.removeEventListener('click', () => {}); // the two are different functions
The ten parts cover the get element, the query selector, the create element, the addEventListener, the this, the removal, the element guard, the custom guard, the target narrowing, and the anti-patterns.
Quick Reference
The Element Hierarchy
| Type | Purpose |
|---|---|
Node | The tree node |
Element | The base element |
HTMLElement | The HTML element |
HTMLInputElement | The input |
HTMLButtonElement | The button |
HTMLDivElement | The div |
The Event Hierarchy
| Type | Purpose |
|---|---|
Event | The base |
UIEvent | The UI base |
MouseEvent | The mouse |
KeyboardEvent | The keyboard |
InputEvent | The input |
FocusEvent | The focus |
The Query Methods
| Method | Return |
|---|---|
getElementById | HTMLElement | null |
querySelector | Element | null (or the generic) |
querySelectorAll | NodeListOf<Element> (or the generic) |
createElement | The specific type |
The addEventListener
| Form | Handler’s Parameter |
|---|---|
addEventListener('click', h) | MouseEvent |
addEventListener('keydown', h) | KeyboardEvent |
addEventListener('input', h) | Event |
addEventListener('custom', h) | Event |
The Type Guards
| Guard | Narrows To |
|---|---|
instanceof HTMLInputElement | HTMLInputElement |
el is HTMLInputElement | HTMLInputElement |
target instanceof HTMLButtonElement | HTMLButtonElement |
closest<HTMLButtonElement>('button') | HTMLButtonElement | null |
The Event Properties
| Property | Type |
|---|---|
event.type | string |
event.target | EventTarget | null |
event.currentTarget | The element’s type |
event.preventDefault() | The method |
event.stopPropagation() | The method |
Best Practices
✅ Do This:
// Check the null from the getElementById
const el = document.getElementById('app');
if (el) el.textContent = 'Hello'; // ✅
// Use the type parameter for the querySelector
const input = document.querySelector<HTMLInputElement>('input'); // ✅
// Use the createElement's specific type
const button = document.createElement('button'); // ✅
// Let the addEventListener narrow the event
element.addEventListener('click', (event) => event.clientX); // ✅
// Use the function for the this
element.addEventListener('click', function (event) { this.disabled = true; }); // ✅
// Use the instanceof for the element narrowing
if (el instanceof HTMLInputElement) el.value = 'text'; // ✅
// Use the same reference for the removal
const handler = (e: MouseEvent) => {};
element.addEventListener('click', handler);
element.removeEventListener('click', handler); // ✅
// Use the custom guard for the reusable check
function isInput(el: Element): el is HTMLInputElement { return el.tagName === 'INPUT'; } // ✅
❌ Don’t Do This:
// Don't forget the null check
const el = document.getElementById('app');
el.textContent = 'Hello'; // ❌ el is possibly null // ⚠️
// Don't use the wrong type parameter
const input = document.querySelector<HTMLInputElement>('div'); // ⚠️
// Don't use the arrow for the this
element.addEventListener('click', () => { this.disabled = true; }); // ⚠️
// Don't use the Event for the specific properties
function onClick(event: Event) { event.clientX; } // ❌ // ⚠️
// Don't cast the target without the check
const target = event.target as HTMLButtonElement; // ⚠️
// Don't use the different function for the removal
element.addEventListener('click', () => {});
element.removeEventListener('click', () => {}); // different // ⚠️
// Don't forget the this parameter in the callback type
type Handler = (event: MouseEvent) => void; // the this is any // ⚠️
Common Pitfalls
| Pitfall | Problem | Solution |
|---|---|---|
| Missing null check | The runtime error | The if (el) guard |
| Wrong type parameter | The wrong type | The correct parameter |
Arrow for the this | The lexical this | Use the function |
Event for the specific | The property missing | Use the specific type |
| Cast without the check | The runtime error | The instanceof |
| Different removal reference | The removal fails | The same variable |
The target without the check | The EventTarget | The instanceof |
The currentTarget vs the target | The wrong element | The currentTarget for the listener’s |
Real-World Examples
1. The get element
const el = document.getElementById('app');
if (el) el.textContent = 'Hello';
2. The query selector
const input = document.querySelector<HTMLInputElement>('input');
3. The create element
const button = document.createElement('button');
4. The addEventListener
element.addEventListener('click', (event) => event.clientX);
5. The this
element.addEventListener('click', function () { this.disabled = true; });
6. The removal
const handler = (e: MouseEvent) => {};
element.addEventListener('click', handler);
element.removeEventListener('click', handler);
7. The element guard
if (el instanceof HTMLInputElement) el.value = 'text';
8. The custom guard
function isInput(el: Element): el is HTMLInputElement {
return el.tagName === 'INPUT';
}
9. The target narrowing
element.addEventListener('click', (event) => {
if (event.target instanceof HTMLButtonElement) {
console.log(event.target.disabled);
}
});
10. The custom element map
declare global {
interface HTMLElementTagNameMap {
'my-element': MyElement;
}
}
Visual: The Element Hierarchy
┌──────────────────────────────────────────────────────────┐
│ EventTarget │
│ └── Node │
│ └── Element │
│ └── HTMLElement │
│ ├── HTMLInputElement │
│ ├── HTMLButtonElement │
│ ├── HTMLDivElement │
│ └── ... │
│ │
│ The specific type has the specific properties. │
│ The HTMLElement has the style, the className, the click.│
│ The HTMLInputElement has the value, the checked. │
│ │
└──────────────────────────────────────────────────────────┘
Visual: The Event Hierarchy
┌──────────────────────────────────────────────────────────┐
│ Event │
│ ├── UIEvent │
│ │ ├── MouseEvent (clientX, clientY, button) │
│ │ └── KeyboardEvent (key, code) │
│ ├── InputEvent (data, inputType) │
│ ├── FocusEvent (relatedTarget) │
│ └── ... │
│ │
│ The specific event has the specific properties. │
│ The addEventListener narrows the event from the name. │
│ │
└──────────────────────────────────────────────────────────┘
Visual: The addEventListener Narrowing
┌──────────────────────────────────────────────────────────┐
│ element.addEventListener('click', (event) => { │
│ // event is MouseEvent │
│ event.clientX; │
│ }); │
│ │
│ element.addEventListener('keydown', (event) => { │
│ // event is KeyboardEvent │
│ event.key; │
│ }); │
│ │
│ element.addEventListener('custom', (event) => { │
│ // event is Event (the general) │
│ }); │
│ │
│ The event name's literal type determines the event's │
│ type. The map is the HTMLElementEventMap. │
│ │
└──────────────────────────────────────────────────────────┘
Visual: The target vs the currentTarget
┌──────────────────────────────────────────────────────────┐
│ container.addEventListener('click', (event) => { │
│ // event.currentTarget is HTMLDivElement │
│ // event.target is EventTarget | null │
│ }); │
│ │
│ THE CLICK ON A CHILD: │
│ │
│ <div id="container"> │
│ <button>Click</button> │
│ </div> │
│ │
│ The click on the button: │
│ event.target = the button │
│ event.currentTarget = the container │
│ │
│ The target is the dispatcher; the currentTarget is │
│ the listener's element. │
│ │
└──────────────────────────────────────────────────────────┘
Visual: The Type Guards
┌──────────────────────────────────────────────────────────┐
│ THE instanceof GUARD │
│ │
│ if (el instanceof HTMLInputElement) { │
│ el.value = 'text'; // el is HTMLInputElement │
│ } │
│ │
│ THE CUSTOM GUARD │
│ │
│ function isInput(el: Element): el is HTMLInputElement { │
│ return el.tagName === 'INPUT'; │
│ } │
│ │
│ if (isInput(el)) { │
│ el.value = 'text'; │
│ } │
│ │
│ Both narrow the type. │
│ The instanceof is the inline; the custom is the reusable.│
│ │
└──────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Element base | HTMLElement |
| Specific elements | HTMLInputElement, etc. |
| Event base | Event |
| Specific events | MouseEvent, KeyboardEvent |
getElementById | HTMLElement | null |
querySelector | Element | null |
createElement | The specific type |
addEventListener | Narrows the event |
event.target | EventTarget | null |
event.currentTarget | The element’s type |
| The guard | instanceof or the custom |
Key takeaways:
- The DOM’s types are declared in the
lib.dom.d.ts— theHTMLElement, theEvent, and the other types are the global, and the compiler loads them when thelibincludes thedom - The element hierarchy is the
Node→ theElement→ theHTMLElement→ the specific — the specific type has the specific properties, and the narrowing is the way to access them - The event hierarchy is the
Event→ theUIEvent→ theMouseEvent/ theKeyboardEvent— the specific event has the specific properties - The
addEventListeneris generic over the event name — the event name’s literal type determines the event’s type, and the handler’s parameter is narrowed - The
thisin the handler is the element — the regular function’sthisis the element, and the arrow’sthisis the lexical - The
event.targetis theEventTarget | null, and theevent.currentTargetis the element — the target is the dispatcher, and the currentTarget is the listener’s element - The
instanceofguard narrows the element to the specific type — the custom guard is the reusable alternative, and theispredicate is the type predicate - The
createElementreturns the specific type from the tag name — theHTMLElementTagNameMapis the map, and the custom elements can augment it - The removal requires the same function reference — the inline arrow cannot be removed, and the variable is the pattern
- The
querySelector‘s type parameter is the assertion — the compiler trusts it, and the wrong type parameter is the runtime error
Remember: The DOM’s types are the browser’s API, and TypeScript declares them precisely. The element hierarchy is the specific properties, the event hierarchy is the specific event data, the addEventListener narrows the event from the name, and the type guards narrow the elements and the targets. The null checks are required, the this is the element with the regular function, and the currentTarget is the listener’s element. The types are the safety, and the DOM is the territory.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!