Agent X-Ray
RuntimeNotesAbout
Notes/代码工程/TypeScript 深度教程/第15章

第15章:浏览器开发 —— DOM、JSX 与资源管理

3 分钟 · 更新于 2026-09-01

第15章:浏览器开发 —— DOM、JSX 与资源管理

本章补齐官方 Handbook 的浏览器侧主题:DOM 类型层次、元素查询与收窄、JSX 类型检查,以及 using / await using 所表达的确定性资源释放。


一、DOM 类型来自 lib.dom.d.ts

浏览器 API 的类型通常由 TypeScript 标准库声明提供:

json
{
  "compilerOptions": {
    "lib": ["ES2022", "DOM", "DOM.Iterable"]
  }
}

Node-only 项目不应无意加入 DOM 类型,否则可能让不存在的浏览器全局变量通过检查。

二、DOM 类型层次

常见关系:

text
EventTarget
└── Node
    └── Element
        └── HTMLElement
            ├── HTMLDivElement
            ├── HTMLInputElement
            └── HTMLButtonElement

越具体的类型拥有越具体的属性。例如 value 属于输入元素,不属于所有 HTMLElement

三、查询元素并处理不存在

ts
const element = document.getElementById("search");
// HTMLElement | null

if (!(element instanceof HTMLInputElement)) {
  throw new Error("#search 必须是 input");
}

console.log(element.value);

这同时验证存在性和真实元素类型,比直接断言更可靠。

四、querySelector 泛型

ts
const form = document.querySelector<HTMLFormElement>("#search-form");

泛型帮助指定期望类型,但仍可能返回 null,且不会在运行时验证选择器匹配的是表单。对不受自己控制的 DOM 仍应使用 instanceof

五、children 与 childNodes

  • children:只包含元素节点。
  • childNodes:还可能包含文本、注释等 Node。
ts
for (const child of element.children) {
  // child: Element
}

选择 API 时应符合真实节点需求。

六、事件类型

ts
const input = document.querySelector("#search");
if (input instanceof HTMLInputElement) {
  input.addEventListener("input", event => {
    const target = event.currentTarget;
    if (target instanceof HTMLInputElement) {
      console.log(target.value);
    }
  });
}

不要无条件断言 event.target。事件可能冒泡,target 与注册监听器的元素不是同一概念。

七、JSX 编译配置

JSX 的解析和输出由 jsx 选项控制,具体值取决于框架和构建工具。.tsx 文件允许 JSX;尖括号类型断言在 TSX 中不可用,应使用 as

tsx
const value = input as HTMLInputElement;

八、JSX 类型检查模型

JSX 需要确定:

  • 内置元素的属性类型。
  • 函数组件或类组件是否可调用/构造。
  • Props 是否匹配。
  • children 是否允许。
  • JSX 表达式结果类型。

这些规则通常由框架提供的 JSX 命名空间和类型声明定义,因此 React、Preact、Vue JSX 等可能不同。

九、内置元素与组件

tsx
<button disabled={true}>保存</button>

button 属于 intrinsic element,由框架声明属性。

tsx
interface ButtonProps {
  label: string;
  onClick(): void;
}

function Button(props: ButtonProps) {
  return <button onClick={props.onClick}>{props.label}</button>;
}

大写名称按值解析为组件,Props 根据函数参数或类实例推导。

十、children 的契约

不要默认所有组件都接受任意 children:

tsx
interface PanelProps {
  title: string;
  children: React.ReactNode;
}

如果组件不应该有 children,就不要为了方便添加宽泛字段。

十一、Symbol 与迭代协议

Symbol.iteratorSymbol.asyncIterator 等 well-known symbols 连接对象与语言协议。实现自定义迭代器时,需要正确返回迭代结果和结束状态;一般业务优先使用数组、Map、Set 和生成器。

unique symbol 可作为真正唯一的类型级键:

ts
const token: unique symbol = Symbol("token");

十二、usingawait using

支持显式资源管理的对象可实现:

ts
interface Disposable {
  [Symbol.dispose](): void;
}
ts
using resource = openResource();
// 离开作用域时调用 Symbol.dispose

异步资源:

ts
await using connection = await openConnection();

这与普通 await 不同:await using 会在离开作用域时等待异步清理。

运行环境支持 语法能否编译、输出如何降级、运行时是否具备相关 Symbol 与辅助代码,取决于 TypeScript 配置和目标环境。必须验证真实产物。

十三、资源生命周期

浏览器资源不只包括文件:

  • 事件监听器。
  • 定时器。
  • WebSocket。
  • Observer。
  • AbortController。
  • MediaStream。

框架组件卸载或作用域结束时必须清理。类型能帮助定义清理函数:

ts
function subscribe(listener: () => void): () => void {
  // 注册
  return () => {
    // 取消
  };
}

十四、常见误区

  1. DOM 查询后直接非空断言。
  2. 用泛型 querySelector 代替运行时验证。
  3. 混淆 targetcurrentTarget
  4. Node 项目意外依赖 DOM 全局类型。
  5. 认为所有 JSX 框架共享 React 的类型规则。
  6. 所有组件都宽泛接受 children。
  7. 添加监听器、定时器或连接后不清理。
  8. 使用 using 语法但不验证目标运行环境。

十五、练习

  1. 安全查询输入框并读取值,不使用 !
  2. 比较 childrenchildNodes
  3. 创建一个带严格 Props 的 TSX 组件。
  4. 实现返回取消函数的事件订阅 API。
  5. 编写实现 Symbol.dispose 的测试资源并观察清理顺序。

十六、总结

  1. DOM 类型层次对应浏览器对象继承关系。
  2. 查询 API 通常返回可空和较宽类型,需要运行时收窄。
  3. JSX 类型规则由具体框架声明提供。
  4. Symbol 连接语言协议,using 表达确定性资源释放。
  5. 监听器、定时器和连接都需要明确生命周期。

请继续阅读:第16章:构建与工具链。


原始资料引用


  • 第14章:JavaScript 渐进类型化
  • 第16章:构建与工具链

本章目录
一、DOM 类型来自 lib.dom.d.ts二、DOM 类型层次三、查询元素并处理不存在四、querySelector 泛型五、children 与 childNodes六、事件类型七、JSX 编译配置八、JSX 类型检查模型九、内置元素与组件十、children 的契约十一、Symbol 与迭代协议十二、using 与 await using十三、资源生命周期十四、常见误区十五、练习十六、总结原始资料引用Related Documents
苏ICP备2025204887号-2