声明文件:连接JavaScript与TypeScript的桥梁
什么是声明文件?
声明文件(.d.ts)是TypeScript中一种特殊的文件,它不包含具体的实现代码,只包含类型声明。这些文件让TypeScript能够理解已有的JavaScript代码库,提供类型检查和智能提示功能。
为什么需要声明文件?
当我们在TypeScript项目中使用第三方JavaScript库时,TypeScript编译器无法识别这些库的类型信息。声明文件解决了这个问题:
typescript
// 没有声明文件时,TypeScript会报错
// Error: Cannot find name 'jQuery'
jQuery('#my-element');
声明语句基础
使用 declare关键字可以告诉TypeScript某个变量或类型的存在:
typescript
// 简单的声明语句
declare var jQuery: (selector: string) => any;
// 现在可以使用jQuery了
jQuery('#my-element');
使用现有的声明文件
大多数流行的JavaScript库都有现成的声明文件,可以通过npm安装:
bash
# 安装jQuery的声明文件
npm install @types/jquery --save-dev
安装后,TypeScript会自动识别这些类型定义,无需额外配置。
自定义声明文件
当使用没有现成声明文件的库时,可以创建自己的声明文件:
typescript
// custom-library.d.ts
declare module 'custom-library' {
export function doSomething(config: any): void;
export interface Config {
apiUrl: string;
timeout?: number;
}
}
然后在代码中使用:
typescript
import { doSomething, Config } from 'custom-library';
const config: Config = {
apiUrl: 'https://api.example.com',
timeout: 5000
};
doSomething(config);
TypeScript内置对象
TypeScript提供了对JavaScript内置对象的类型支持,这些对象可以直接在代码中使用而无需额外声明。
ECMAScript内置对象
TypeScript完整支持ECMAScript标准定义的内置对象:
typescript
// Boolean对象
let boolObj: Boolean = new Boolean(true);
let primitiveBool: boolean = Boolean(true);
// Number对象
let numObj: Number = new Number(42);
let primitiveNum: number = Number('42');
// String对象
let strObj: String = new String('hello');
let primitiveStr: string = String(123);
// Date对象
let now: Date = new Date();
let timestamp: number = now.getTime();
// RegExp对象
let regex: RegExp = /^[a-zA-Z]+$/;
let regexObj: RegExp = new RegExp('^\\d+$');
// Error对象
let error: Error = new Error('Something went wrong');
重要区别:注意基本类型与包装对象的区别。基本类型(boolean, number, string)与它们的包装对象(Boolean, Number, String)是不同的类型,不能直接互换使用。
DOM和BOM内置对象
TypeScript还提供了对浏览器环境对象的完整类型支持:
DOM操作
typescript
// 获取元素
const container: HTMLElement = document.getElementById('app')!;
const buttons: NodeList = document.querySelectorAll('.btn');
// 创建元素
const newDiv: HTMLDivElement = document.createElement('div');
newDiv.className = 'panel';
newDiv.textContent = 'Hello World';
// 添加事件监听
document.addEventListener('click', (event: MouseEvent) => {
console.log(`Clicked at: ${event.clientX}, ${event.clientY}`);
});
// 使用DocumentFragment提升性能
const fragment: DocumentFragment = document.createDocumentFragment();
for (let i = 0; i < 10; i++) {
const item = document.createElement('li');
item.textContent = `Item ${i}`;
fragment.appendChild(item);
}
container.appendChild(fragment);
常用DOM类型
typescript
// 元素类型
const div: HTMLDivElement = document.createElement('div');
const input: HTMLInputElement = document.createElement('input');
const img: HTMLImageElement = document.createElement('img');
// 事件类型
element.addEventListener('keydown', (event: KeyboardEvent) => {
if (event.key === 'Enter') {
// 处理回车键
}
});
element.addEventListener('submit', (event: SubmitEvent) => {
event.preventDefault();
// 处理表单提交
});
BOM对象
typescript
// 使用Window对象
const width: number = window.innerWidth;
const height: number = window.innerHeight;
// 使用Location对象
const currentUrl: string = window.location.href;
const hostname: string = window.location.hostname;
// 使用Navigator对象
const userAgent: string = window.navigator.userAgent;
const isMobile: boolean = /Mobile/.test(userAgent);
// 使用History对象
window.history.pushState({}, '', '/new-page');
// 使用Storage
localStorage.setItem('theme', 'dark');
const theme: string | null = localStorage.getItem('theme');
// 定时器
const timerId: number = setTimeout(() => {
console.log('Delayed message');
}, 1000);
// 清除定时器
clearTimeout(timerId);
实践应用场景
场景1:增强第三方库的类型安全
typescript
// 假设我们使用一个图表库,但没有类型定义
declare module 'simple-charts' {
export interface ChartConfig {
type: 'bar' | 'line' | 'pie';
data: Array<number>;
labels?: Array<string>;
colors?: Array<string>;
}
export function createChart(container: HTMLElement, config: ChartConfig): void;
export function updateChart(data: Array<number>): void;
}
// 使用
import { createChart, ChartConfig } from 'simple-charts';
const config: ChartConfig = {
type: 'bar',
data: [12, 19, 3, 5, 2],
labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple']
};
createChart(document.getElementById('chart')!, config);
场景2:安全操作DOM
typescript
// 安全的DOM操作函数
function getElement<T extends HTMLElement>(selector: string): T | null {
return document.querySelector(selector);
}
function getRequiredElement<T extends HTMLElement>(selector: string): T {
const element = document.querySelector(selector);
if (!element) {
throw new Error(`Element with selector "${selector}" not found`);
}
return element as T;
}
// 使用
const form = getRequiredElement<HTMLFormElement>('#user-form');
const submitButton = getElement<HTMLButtonElement>('#submit-btn');
form.addEventListener('submit', (event: SubmitEvent) => {
event.preventDefault();
if (submitButton) {
submitButton.disabled = true;
submitButton.textContent = 'Processing...';
}
// 处理表单提交
});
场景3:类型安全的本地存储
typescript
// 包装localStorage,提供类型安全
class TypedStorage<T> {
constructor(private key: string) {}
get(): T | null {
const item = localStorage.getItem(this.key);
return item ? JSON.parse(item) : null;
}
set(value: T): void {
localStorage.setItem(this.key, JSON.stringify(value));
}
remove(): void {
localStorage.removeItem(this.key);
}
}
// 使用
interface UserPreferences {
theme: 'light' | 'dark';
language: string;
notifications: boolean;
}
const userPrefsStorage = new TypedStorage<UserPreferences>('user-preferences');
// 保存设置
userPrefsStorage.set({
theme: 'dark',
language: 'zh-CN',
notifications: true
});
// 读取设置
const prefs = userPrefsStorage.get();
if (prefs) {
console.log(`Current theme: ${prefs.theme}`);
}
最佳实践与注意事项
-
优先使用现成的声明文件:在npm上搜索
@types/包名,大多数流行库都有现成的类型定义 -
合理组织自定义声明文件:
- 将全局声明放在
globals.d.ts中 - 按模块组织的声明放在各自模块的
.d.ts文件中 - 使用三斜线指令引用依赖:
/// <reference types="some-library" />
- 将全局声明放在
-
区分基本类型和包装对象:
typescript// 正确:使用基本类型 let isDone: boolean = false; let count: number = 42; let name: string = "TypeScript"; // 避免:不必要的包装对象 let isDone: Boolean = new Boolean(false); // 不推荐 -
安全访问DOM元素:
typescript// 使用非空断言时要小心 const element = document.getElementById('my-element')!; // 更好的做法:检查元素是否存在 const element = document.getElementById('my-element'); if (element) { // 安全操作元素 } -
利用类型断言处理不确定的类型:
typescript// 当TypeScript无法推断正确类型时 const canvas = document.getElementById('my-canvas') as HTMLCanvasElement; const context = canvas.getContext('2d') as CanvasRenderingContext2D;