赋予网页健壮的灵魂 —— TypeScript(下)
7 DOM 操作与类型定义:赋予网页真正的交互
回到最初的目标:让网页动起来。在 TypeScript 中,我们如何安全地操作 HTML 元素并响应用户的交互呢?TypeScript 提供了内置的类型定义来描述浏览器环境中的各种对象(如 document
, window
, DOM 元素,事件对象等),这使得我们在操作 DOM 时也能享受到类型检查带来的便利和安全。
7.1 获取 DOM 元素及其类型
当你使用像 document.getElementById()
或 document.querySelector()
这样的方法获取 DOM 元素时,TypeScript 会根据方法签名返回一个特定的类型。
document.getElementById()
返回HTMLElement | null
。document.querySelector()
返回Element | null
(更通用的类型)。
它们返回的可能是找到的元素类型(如果是特定标签,会有更精确的类型,比如 HTMLDivElement
for <div>
),或者在找不到元素时返回 null
。
// seventh.ts// 获取一个 div 元素
const myDiv = document.getElementById("myDiv");// myDiv 的类型是 HTMLElement | null
if (myDiv) {// 在这个 if 块中,myDiv 的类型被缩小为 HTMLElementmyDiv.textContent = "你好,DOM!";// HTMLElement 类型有通用的属性和方法console.log("Div 元素的标签名:", myDiv.tagName);
} else {console.error("找不到 id 为 myDiv 的元素");
}// 获取一个输入框元素
const myInput = document.getElementById("myInput");// myInput 的类型是 HTMLElement | null
if (myInput) {// 如果确定它是 input 元素,并且你需要访问 input 特有的属性 (如 value),// 你需要使用类型断言或者更精确的选择器方法。// 直接访问 value 会报错,因为 HTMLElement 上没有 value 属性// console.log(myInput.value); // 编译时报错// 使用类型断言const inputElement = myInput as HTMLInputElement;console.log("输入框的值 (断言):", inputElement.value);// 或者使用更精确的查询方法(如果存在)或结合类型守卫(后面会讲)// const inputElement2 = document.querySelector<HTMLInputElement>("#myInput"); // 泛型方法// if (inputElement2) {// console.log("输入框的值 (querySelector 泛型):", inputElement2.value);// }
}
7.2 类型断言用于 DOM 元素
当你明确知道获取到的元素的具体类型时(比如你确定一个通过 ID 获取的元素就是 <canvas>
),可以使用类型断言将其断言为更精确的类型,以便访问该元素特有的属性和方法。
// seventh.ts (接着上面的代码)// 假设 HTML 中有一个 <canvas id="myCanvas"></canvas>
const canvas = document.getElementById("myCanvas") as HTMLCanvasElement;// 现在 TypeScript 知道 canvas 是 HTMLCanvasElement 类型,可以安全地访问 getContext 方法
if (canvas) {const ctx = canvas.getContext("2d");if (ctx) {ctx.fillStyle = 'red';ctx.fillRect(10, 10, 50, 50);}
}
7.3 事件处理
处理用户交互依赖于事件。TypeScript 为各种事件提供了类型定义,这使得在事件处理函数中访问事件对象 (event
或 e
) 的属性时也能获得类型提示和检查。
// seventh.ts (接着上面的代码)const myButton = document.getElementById("myButton"); // 假设 HTML 中有一个 <button id="myButton">点击我</button>if (myButton) {// 添加点击事件监听器myButton.addEventListener("click", (event: MouseEvent) => {// event 参数是 MouseEvent 类型,具有 clientX, clientY 等属性console.log("按钮被点击了!");console.log("点击位置:", event.clientX, event.clientY);// event 对象是 Event 或其子类型event.preventDefault(); // 阻止默认行为});
}const myTextInput = document.getElementById("myTextInput") as HTMLInputElement; // 假设 HTML 中有一个 <input type="text" id="myTextInput">if (myTextInput) {// 添加键盘按下事件监听器myTextInput.addEventListener("keydown", (event: KeyboardEvent) => {// event 参数是 KeyboardEvent 类型,具有 key, code 等属性console.log("键盘按下:", event.key, event.code);if (event.key === 'Enter') {console.log("按下了 Enter 键,当前输入框的值:", myTextInput.value);}});
}
常见的事件类型:Event
, MouseEvent
, KeyboardEvent
, ChangeEvent
, SubmitEvent
等。
7.4 获取第三方库的类型定义 (@types
)
很多流行的 JavaScript 库(如 jQuery, React, Lodash 等)最初并不是用 TypeScript 编写的。为了让 TypeScript 用户能够享受到类型检查和智能提示,社区维护了大量的类型声明文件 (.d.ts
文件)。这些文件只包含类型信息,不包含具体的实现代码。
这些类型声明文件通常发布在 @types
组织下,你可以使用 npm 或 yarn 安装它们。
例如,如果你想在 TypeScript 项目中使用 Lodash 库:
- 安装 Lodash:
npm install lodash
- 安装 Lodash 的类型声明文件:
npm install @types/lodash --save-dev
现在,你就可以在 TypeScript 代码中导入并使用 Lodash 了,TypeScript 会根据 @types/lodash
中的类型信息为你提供强大的类型检查和代码补全。
// seventh.ts (接着上面的代码)// 我们通过declare const _: any;告诉TypeScript全局lodash变量的存在
declare const _: any;const numbers = [1, 2, 3, 4, 5];
const sumOfNumbers = _.sum(numbers); // TypeScript 知道 _.sum 接受一个 number 数组并返回一个 number
console.log("数字总和 (lodash):", sumOfNumbers);const users = [{ 'user': 'barney', 'age': 36, 'active': true },{ 'user': 'fred', 'age': 40, 'active': false }
];const activeUsers = _.filter(users, { 'active': true }); // TypeScript 知道 filter 的用法和返回类型
console.log("活跃用户 (lodash):", activeUsers);
当你安装一个本身就是用 TypeScript 编写的库时(比如 React 的新版本、Vue 3 等),它们通常已经自带了类型声明文件,你无需额外安装 @types
包。
编译 seventh.ts
:
tsc seventh.ts
会生成 seventh.js
文件。然后在 HTML 中引入 seventh.js
,并确保你的 HTML 中有相应的 div
, input
, button
, canvas
元素。
<!DOCTYPE html>
<html>
<head> <title>TS DOM 操作</title> </head>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
<body><div id="myDiv">这是一个 div</div><input type="text" id="myInput" value="默认值"><input type="text" id="myTextInput"><button id="myButton">点击触发事件</button><canvas id="myCanvas" width="200" height="100" style="border: 1px solid black;"></canvas><script src="dist/seventh.js"></script> </body>
</html>
小结: TypeScript 提供了丰富的内置类型来描述 DOM 和事件对象,结合类型断言和 @types
声明文件,可以让我们在进行客户端 DOM 操作和使用第三方库时,编写出类型安全、易于维护的代码。
练习:
- 创建一个 HTML 文件,包含一个段落
<p id="myParagraph">原始文本</p>
和一个按钮<button id="changeTextBtn">改变文本</button>
。 - 编写 TypeScript 代码:获取段落和按钮元素,为按钮添加点击事件监听器。在事件处理函数中,获取段落元素,并将其
textContent
修改为“文本已更新!”。确保在 TypeScript 代码中使用了合适的类型。 - 创建一个 HTML 文件,包含一个图像元素
<img id="myImage" src="" alt="图片">
和一个输入框<input type="text" id="imageUrlInput" placeholder="输入图片 URL">
。 - 编写 TypeScript 代码:获取图像元素和输入框。为输入框添加
change
事件监听器(当输入框的值改变并失去焦点时触发)。在事件处理函数中,获取输入框的值,并将其设置为图像元素的src
属性。 - (进阶)选择一个你感兴趣的流行 JavaScript 库(如 Moment.js),尝试使用 npm 安装它以及对应的
@types
包,然后在 TypeScript 代码中导入并使用该库的功能,体验类型提示。