typescript常用的dom 元素类型

35 阅读5分钟

在 TypeScript 中处理 DOM 操作时,有一整套完善的类型系统。下面我将系统地整理前端开发中最常用的 DOM 元素类型。

🎯 基础 DOM 类型体系

1. 顶层类型

// 所有 DOM 节点的基类
let node: Node = document.createElement('div')

// 所有 HTML 元素的基类
let element: Element = document.querySelector('div')!

// 所有具体的 HTML 元素都继承自 HTMLElement
let htmlElement: HTMLElement = document.createElement('div')

// 文档对象
let doc: Document = document

// 窗口对象
let win: Window = window

📦 常用具体元素类型

1. 输入控件类

// 输入框
let input: HTMLInputElement = document.createElement('input')
input.value = 'hello'
input.checked = true
input.type = 'password'
input.placeholder = '请输入'
input.files // FileList | null

// 文本域
let textarea: HTMLTextAreaElement = document.createElement('textarea')
textarea.value = '多行文本'
textarea.rows = 5
textarea.cols = 30

// 按钮
let button: HTMLButtonElement = document.createElement('button')
button.disabled = true
button.type = 'submit'

// 选择框
let select: HTMLSelectElement = document.createElement('select')
let option: HTMLOptionElement = document.createElement('option')
select.value = 'option1'
select.selectedIndex = 0
select.options // HTMLOptionsCollection

2. 容器和布局类

// 通用块级容器
let div: HTMLDivElement = document.createElement('div')
let span: HTMLSpanElement = document.createElement('span')
let section: HTMLElement = document.createElement('section')  // 直接用 HTMLElement
let article: HTMLElement = document.createElement('article')

// 列表
let ul: HTMLUListElement = document.createElement('ul')
let ol: HTMLOListElement = document.createElement('ol')
let li: HTMLLIElement = document.createElement('li')

// 表格相关
let table: HTMLTableElement = document.createElement('table')
let tr: HTMLTableRowElement = document.createElement('tr')
let td: HTMLTableCellElement = document.createElement('td')
let th: HTMLTableCellElement = document.createElement('th')
let tbody: HTMLTableSectionElement = document.createElement('tbody')
let thead: HTMLTableSectionElement = document.createElement('thead')

// 表单
let form: HTMLFormElement = document.createElement('form')
form.action = '/submit'
form.method = 'POST'
form.elements // HTMLFormControlsCollection

3. 媒体类

// 图片
let img: HTMLImageElement = document.createElement('img')
img.src = '/image.jpg'
img.alt = '描述'
img.width = 100
img.height = 100
img.complete // 图片是否加载完成

// 音频
let audio: HTMLAudioElement = document.createElement('audio')
audio.src = '/audio.mp3'
audio.volume = 0.5
audio.play()
audio.pause()

// 视频
let video: HTMLVideoElement = document.createElement('video')
video.src = '/video.mp4'
video.poster = '/poster.jpg'
video.width = 640
video.height = 360
video.playbackRate = 1.5

// 画布
let canvas: HTMLCanvasElement = document.createElement('canvas')
let ctx: CanvasRenderingContext2D | null = canvas.getContext('2d')

4. 链接和元数据类

// 链接
let a: HTMLAnchorElement = document.createElement('a')
a.href = 'https://example.com'
a.target = '_blank'
a.download = 'file.pdf'

// 图片链接
let area: HTMLAreaElement = document.createElement('area')
area.shape = 'rect'
area.coords = '0,0,100,100'

// Meta 信息
let meta: HTMLMetaElement = document.createElement('meta')
meta.name = 'description'
meta.content = '页面描述'

// 链接资源
let link: HTMLLinkElement = document.createElement('link')
link.rel = 'stylesheet'
link.href = '/style.css'

🎨 特定功能的元素类型

1. 进度和度量

// 进度条
let progress: HTMLProgressElement = document.createElement('progress')
progress.value = 50
progress.max = 100

// 度量
let meter: HTMLMeterElement = document.createElement('meter')
meter.value = 0.6
meter.min = 0
meter.max = 1
meter.low = 0.3
meter.high = 0.8
meter.optimum = 0.5

2. 嵌入内容

// iframe
let iframe: HTMLIFrameElement = document.createElement('iframe')
iframe.src = '/other-page.html'
iframe.contentWindow // Window | null
iframe.contentDocument // Document | null

// 嵌入对象
let object: HTMLObjectElement = document.createElement('object')
object.data = '/file.pdf'
object.type = 'application/pdf'

// 嵌入脚本
let script: HTMLScriptElement = document.createElement('script')
script.src = '/main.js'
script.async = true
script.defer = true

3. 表单特有元素

// 单选/复选
let radio: HTMLInputElement = document.createElement('input')
radio.type = 'radio'
radio.name = 'gender'
radio.value = 'male'
radio.checked = true

let checkbox: HTMLInputElement = document.createElement('input')
checkbox.type = 'checkbox'
checkbox.checked = true
checkbox.indeterminate = false

// 文件上传
let fileInput: HTMLInputElement = document.createElement('input')
fileInput.type = 'file'
fileInput.multiple = true
fileInput.accept = 'image/*'

// 隐藏输入
let hidden: HTMLInputElement = document.createElement('input')
hidden.type = 'hidden'
hidden.value = 'secret-data'

// 颜色选择器
let color: HTMLInputElement = document.createElement('input')
color.type = 'color'
color.value = '#ff0000'

// 范围滑块
let range: HTMLInputElement = document.createElement('input')
range.type = 'range'
range.min = 0
range.max = 100
range.step = 5
range.value = '50'

🔍 类型查询和断言

1. 获取 DOM 元素

// 类型断言
const canvas = document.getElementById('canvas') as HTMLCanvasElement
const input = <HTMLInputElement>document.querySelector('input[type="text"]')

// 安全的类型检查
function isInputElement(el: HTMLElement): el is HTMLInputElement {
  return el.tagName === 'INPUT'
}

// 使用实例
const element = document.getElementById('myElement')
if (element instanceof HTMLInputElement) {
  element.value // TypeScript 知道这是 input
} else if (element instanceof HTMLTextAreaElement) {
  element.value // 这也是 input 类型
}

// 更好的方式:使用类型守卫
function getInput(id: string): HTMLInputElement | null {
  const el = document.getElementById(id)
  return el instanceof HTMLInputElement ? el : null
}

2. 集合类型

// NodeList
const nodes: NodeListOf<HTMLElement> = document.querySelectorAll('.item')
nodes.forEach(node => node.style.color = 'red')

// HTMLCollection
const forms: HTMLCollectionOf<HTMLFormElement> = document.forms
const images: HTMLCollectionOf<HTMLImageElement> = document.images

// 特定类型的集合
const allInputs = document.querySelectorAll<HTMLInputElement>('input')
// allInputs 的类型是 NodeListOf<HTMLInputElement>

// 类型化的集合
interface MyElements {
  'username': HTMLInputElement
  'submitBtn': HTMLButtonElement
  'avatar': HTMLImageElement
}

function getElement<K extends keyof MyElements>(id: K): MyElements[K] | null {
  return document.getElementById(id) as MyElements[K] | null
}

⚡ 事件对象类型

1. 常见事件类型

// 鼠标事件
function handleMouse(e: MouseEvent) {
  e.clientX, e.clientY  // 鼠标坐标
  e.button  // 按下的鼠标键
  e.ctrlKey, e.shiftKey  // 修饰键
}

// 键盘事件
function handleKey(e: KeyboardEvent) {
  e.key  // 按下的键值
  e.code  // 物理按键代码
  e.altKey  // 是否按下 Alt
  e.repeat  // 是否长按重复
}

// 焦点事件
function handleFocus(e: FocusEvent) {
  e.relatedTarget  // 上一个/下一个焦点元素
}

// 表单事件
function handleSubmit(e: Event) {
  e.preventDefault()
  const form = e.target as HTMLFormElement
}

// 拖拽事件
function handleDrag(e: DragEvent) {
  e.dataTransfer?.setData('text/plain', 'data')
}

// 剪贴板事件
function handlePaste(e: ClipboardEvent) {
  const text = e.clipboardData?.getData('text/plain')
}

2. Vue 3 中的事件类型

<script setup lang="ts">
// 原生 DOM 事件
const handleClick = (e: MouseEvent) => {
  console.log(e.clientX)
}

// Input 事件
const handleInput = (e: Event) => {
  const target = e.target as HTMLInputElement
  console.log(target.value)
}

// Change 事件
const handleChange = (e: Event) => {
  const target = e.target as HTMLSelectElement
  console.log(target.value)
}

// 键盘事件
const handleKeyDown = (e: KeyboardEvent) => {
  if (e.key === 'Enter') {
    console.log('回车键按下')
  }
}

// 自定义组件事件
interface Emits {
  (e: 'update', value: string): void
  (e: 'close', reason: 'click' | 'esc'): void
}

const emit = defineEmits<Emits>()
</script>

<template>
  <input @click="handleClick">
  <input @input="handleInput">
  <select @change="handleChange">
    <option value="1">选项1</option>
  </select>
  <input @keydown="handleKeyDown">
</template>

🛠️ 实用工具类型

1. 内置的 DOM 工具类型

// 获取元素属性的类型
type InputValue = HTMLInputElement['value']  // string
type ButtonType = HTMLButtonElement['type']  // 'button' | 'submit' | 'reset'
type ElementTag = HTMLElement['tagName']  // string

// Partial 应用于 DOM 配置
interface CanvasConfig {
  width: number
  height: number
  context: CanvasRenderingContext2D
}
const config: Partial<CanvasConfig> = { width: 800 }

// 只读 DOM 引用
const readonlyElement: Readonly<HTMLElement> = document.body
// readonlyElement.innerHTML = '' // ❌ 错误

2. 自定义 DOM 类型

// 自定义数据属性
interface DatasetMap {
  userId: string
  role: 'admin' | 'user'
  theme: 'light' | 'dark'
}

function setDataset<T extends HTMLElement>(
  el: T,
  data: Partial<{ [K in keyof DatasetMap]: string }>
) {
  Object.entries(data).forEach(([key, value]) => {
    el.dataset[key] = value
  })
}

const div = document.createElement('div')
setDataset(div, { userId: '123', role: 'admin' })
// div.dataset.userId 有类型提示

// 带状态的自定义元素
interface StatefulElement<T> extends HTMLElement {
  state: T
  setState: (newState: Partial<T>) => void
}

interface ButtonState {
  loading: boolean
  count: number
}

const btn = document.createElement('button') as StatefulElement<ButtonState>
btn.state = { loading: false, count: 0 }
btn.setState({ loading: true })

📝 实际开发模式

1. 工厂函数模式

// 类型安全的元素创建
function createElement<K extends keyof HTMLElementTagNameMap>(
  tagName: K,
  props?: Partial<HTMLElementTagNameMap[K]>
): HTMLElementTagNameMap[K] {
  const el = document.createElement(tagName)
  if (props) {
    Object.assign(el, props)
  }
  return el
}

// 使用示例
const button = createElement('button', {
  textContent: '点击',
  disabled: false,
  className: 'btn-primary'
})

const input = createElement('input', {
  type: 'email',
  placeholder: '输入邮箱',
  value: ''
})

2. 类型守卫工具

// DOM 类型守卫集合
const domGuards = {
  isInput: (el: Element | null): el is HTMLInputElement => 
    el?.tagName === 'INPUT',
  
  isButton: (el: Element | null): el is HTMLButtonElement => 
    el?.tagName === 'BUTTON',
  
  isSelect: (el: Element | null): el is HTMLSelectElement => 
    el?.tagName === 'SELECT',
  
  isTextArea: (el: Element | null): el is HTMLTextAreaElement => 
    el?.tagName === 'TEXTAREA',
  
  isForm: (el: Element | null): el is HTMLFormElement => 
    el?.tagName === 'FORM'
}

// 使用
const element = document.getElementById('myInput')
if (domGuards.isInput(element)) {
  element.value = '类型安全'
}

3. Vue 3 中的 DOM 模板引用

<script setup lang="ts">
import { ref, onMounted } from 'vue'

// 基本元素引用
const inputRef = ref<HTMLInputElement | null>(null)
const divRef = ref<HTMLDivElement | null>(null)
const canvasRef = ref<HTMLCanvasElement | null>(null)

// 多个元素引用
const itemRefs = ref<HTMLElement[]>([])

// 组件引用
import Modal from './Modal.vue'
const modalRef = ref<InstanceType<typeof Modal> | null>(null)

onMounted(() => {
  // 类型安全的方法调用
  inputRef.value?.focus()
  canvasRef.value?.getContext('2d')
  modalRef.value?.open()
})
</script>

<template>
  <input ref="inputRef" type="text">
  <div ref="divRef" class="container"></div>
  <canvas ref="canvasRef"></canvas>
  
  <!-- 多个元素 -->
  <div 
    v-for="item in 5" 
    :ref="el => itemRefs.push(el as HTMLElement)"
    :key="item"
  >
    Item {{ item }}
  </div>
  
  <Modal ref="modalRef" />
</template>

📊 类型层次结构

EventTarget (所有事件目标的基类)
    ├── Node (所有 DOM 节点的基类)
    │   ├── Element (所有元素的基类)
    │   │   ├── HTMLElement (HTML 元素的基类)
    │   │   │   ├── HTMLInputElement
    │   │   │   ├── HTMLButtonElement
    │   │   │   ├── HTMLDivElement
    │   │   │   └── ...
    │   │   └── SVGElement (SVG 元素)
    │   ├── Text (文本节点)
    │   └── Comment (注释节点)
    ├── Window (窗口对象)
    └── Document (文档对象)

掌握这些 DOM 类型,可以让你在处理浏览器 API 时获得完整的类型支持和智能提示,减少运行时错误。