类型声明文件编写
类型声明文件的作用
类型声明文件(.d.ts)为TypeScript提供了类型信息,让开发者能够为JavaScript库或模块添加静态类型检查。当使用第三方JavaScript库时,如果没有类型声明文件,TypeScript编译器会报错。声明文件不包含具体实现,只包含类型定义。
// 假设有一个JavaScript库mathUtils.js
function add(a, b) {
return a + b;
}
// 对应的声明文件mathUtils.d.ts
declare function add(a: number, b: number): number;
声明文件的创建方式
创建声明文件主要有三种方式:
- 手动编写:适用于小型项目或自定义模块
- 使用工具自动生成:如
dts-gen
可以基于现有JavaScript代码生成声明文件 - 从DefinitelyTyped获取:大多数流行库都有社区维护的类型声明
手动创建声明文件示例:
// myLib.d.ts
declare namespace MyLib {
interface Config {
timeout: number;
retries: number;
}
function init(config: Config): void;
function fetchData(url: string): Promise<any>;
}
模块声明与全局声明
根据使用场景,声明文件可以分为模块声明和全局声明:
模块声明
适用于CommonJS、AMD、ES模块等模块系统:
// 模块声明
declare module 'module-name' {
export interface Options {
debug?: boolean;
}
export function create(options?: Options): void;
}
全局声明
当库通过<script>
标签引入时,需要全局声明:
// 全局变量声明
declare const VERSION: string;
// 全局函数声明
declare function greet(name: string): void;
// 全局类声明
declare class Utils {
static formatDate(date: Date): string;
}
类型合并与扩展
TypeScript支持通过声明合并来扩展已有类型:
接口合并
// 原始声明
interface User {
name: string;
}
// 扩展声明
interface User {
age: number;
}
// 合并后相当于
interface User {
name: string;
age: number;
}
命名空间合并
// 原始命名空间
namespace API {
export function get(url: string): Promise<any>;
}
// 扩展命名空间
namespace API {
export function post(url: string, data: any): Promise<any>;
}
常见类型声明模式
函数重载声明
declare function createElement(tag: 'div'): HTMLDivElement;
declare function createElement(tag: 'span'): HTMLSpanElement;
declare function createElement(tag: string): HTMLElement;
类声明
declare class Animal {
constructor(name: string);
name: string;
move(distance: number): void;
}
泛型类型
declare interface Response<T = any> {
data: T;
status: number;
}
declare function fetch<T>(url: string): Promise<Response<T>>;
处理复杂类型
条件类型
declare type NonNullable<T> = T extends null | undefined ? never : T;
declare type Flatten<T> = T extends Array<infer U> ? U : T;
映射类型
declare type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
declare type Partial<T> = {
[P in keyof T]?: T[P];
};
声明文件中的高级特性
三斜线指令
用于声明文件之间的依赖关系:
/// <reference types="node" />
/// <reference path="other.d.ts" />
全局修改
通过declare global
在模块中扩展全局作用域:
// 在模块声明文件中
export {};
declare global {
interface Window {
myCustomProp: string;
}
}
处理第三方库的特殊情况
模块补丁
当需要扩展第三方模块时:
import { OriginalModule } from 'some-module';
declare module 'some-module' {
interface OriginalModule {
newMethod(): void;
}
}
默认导出处理
// 对于CommonJS模块
declare module 'module-name' {
const value: any;
export = value;
}
// 对于ES模块默认导出
declare module 'es-module' {
const defaultValue: any;
export default defaultValue;
}
类型声明的最佳实践
- 优先使用接口而非类型别名,除非需要联合类型或元组
- 为公共API提供完整的类型定义
- 使用JSDoc注释增强类型声明
- 保持声明文件与实现同步更新
- 为复杂类型提供示例说明
/**
* 计算两个数的和
* @param a - 第一个加数
* @param b - 第二个加数
* @returns 两个数的和
*/
declare function add(a: number, b: number): number;
测试类型声明文件
可以使用tsd
等工具测试类型声明:
import { expectType } from 'tsd';
expectType<string>(add(1, 2)); // 这会报错,因为add返回number
发布类型声明文件
当发布包含类型声明的包时,有两种方式:
- 直接在包中包含
.d.ts
文件,并在package.json
中指定types
字段 - 发布到DefinitelyTyped,通过
@types
作用域提供
package.json
配置示例:
{
"name": "my-package",
"version": "1.0.0",
"types": "./dist/index.d.ts",
"files": ["dist"]
}
处理没有类型声明的库
对于没有类型声明的库,可以创建types
目录并在tsconfig.json
中配置:
{
"compilerOptions": {
"typeRoots": ["./node_modules/@types", "./types"]
}
}
然后在types
目录下创建对应的声明文件:
types/
some-library/
index.d.ts
类型声明中的常见问题
类型循环引用
解决方案:
// a.d.ts
import type { B } from './b';
export interface A {
b: B;
}
// b.d.ts
import type { A } from './a';
export interface B {
a?: A;
}
动态属性访问
declare interface DynamicObject {
[key: string]: any;
}
// 更精确的类型
declare interface StringMap<T> {
[key: string]: T;
}
类型声明与性能优化
过多的类型声明可能影响编译性能。可以通过以下方式优化:
- 使用
import type
避免引入实际代码 - 将大型声明拆分为多个文件
- 避免过度使用条件类型和复杂类型运算
// 使用类型导入
import type { SomeType } from 'some-module';
// 而不是
import { SomeType } from 'some-module';
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn