Skip to content

第10章 类型声明与声明文件

本章定位:学习TypeScript的类型声明系统,包括.d.ts声明文件的编写、内置类型声明的使用、DefinitelyTyped社区类型定义,以及如何创建自定义类型声明。


声明文件(Declaration Files)是以.d.ts为扩展名的文件,用于为JavaScript代码提供类型信息。TypeScript可以通过声明文件了解JavaScript库的类型结构。

math.js
function add(a, b) {
return a + b;
}
module.exports = { add };
// math.d.ts
export function add(a: number, b: number): number;
// 没有声明文件时
import { add } from "./math";
add("1", "2"); // 编译时没有类型检查
// 有声明文件时
add("1", "2"); // 编译错误!Argument of type 'string' is not assignable to parameter of type 'number'

当TypeScript项目中找到顶级.d.ts文件,会将其内容作为全局声明:

// global.d.ts(放在项目根目录或src目录)
declare const APP_VERSION: string;
declare function formatDate(date: Date): string;
interface Config {
apiUrl: string;
timeout: number;
}
declare const config: Config;

TypeScript包含JavaScript标准库的类型声明:

// String
"hello".toUpperCase();
String.prototype.trim();
// Number
Math.PI;
Number.parseInt("42");
// Boolean
const flag: boolean = true;
// Array
const arr: number[] = [1, 2, 3];
arr.map(x => x * 2);
// Object
Object.keys({ a: 1, b: 2 });
// Promise
const promise = new Promise<string>((resolve) => {
resolve("done");
});
// Window
window.addEventListener("click", (event) => {
console.log(event.clientX, event.clientY);
});
// Document
document.createElement("div");
document.querySelector(".container");
// HTMLElement
const element = document.getElementById("myInput") as HTMLInputElement;
element.value;

在tsconfig.json中指定包含的库:

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

常用lib选项:

  • "ES2020" - ECMAScript 2020特性
  • "ES2021" - ECMAScript 2021特性
  • "DOM" - DOM类型
  • "DOM.Iterable" - DOM迭代器
  • "WebWorker" - Web Worker类型
  • "node" - Node.js类型

DefinitelyTyped为npm包提供类型声明:

Terminal window
npm install --save-dev @types/node @types/react @types/express

安装后,TypeScript自动识别这些类型。

访问 https://github.com/DefinitelyTyped/DefinitelyTyped 查找类型包。

常用@types包:

  • @types/node - Node.js运行时
  • @types/react - React
  • @types/express - Express
  • @types/lodash - Lodash
  • @types/jest - Jest测试框架
  • @types/mocha - Mocha测试框架
Terminal window
# 安装与主包版本匹配的@types
npm install react@18
npm install --save-dev @types/react@18
@types/
├── node/
│ ├── index.d.ts
│ ├── ts3.6/
│ │ └── index.d.ts
│ └── package.json
// @types/node/package.json
{
"name": "@types/node",
"version": "20.0.0",
"types": "index.d.ts"
}

src/utils.ts
export function formatDate(date: Date): string {
return date.toISOString().split("T")[0];
}
// src/utils.d.ts
export function formatDate(date: Date): string;
node_modules/some-lib/index.js
function someLib(options) {
return { result: options.value };
}
// types/some-lib/index.d.ts
declare module "some-lib" {
interface Options {
value: string;
}
function someLib(options: Options): { result: string };
export = someLib;
}
// 扩展已有模块
declare module "existing-module" {
export function newFunction(): void;
}
// 扩展全局
declare global {
interface Array<T> {
shuffle(): T[];
}
}
// 无导入的扩展需要/// <reference types="..." />
/// <reference types="extended-types" />
math.d.ts
declare namespace MathUtils {
function add(a: number, b: number): number;
function subtract(a: number, b: number): number;
namespace Constants {
const PI: number;
const E: number;
}
}
// 使用
MathUtils.add(1, 2);
MathUtils.Constants.PI;

10.5 声明合并(declaration merging)

Section titled “10.5 声明合并(declaration merging)”

同名接口自动合并:

user.ts
interface User {
name: string;
}
// user-extension.ts
interface User {
email: string;
age: number;
}
// 合并后等价于
interface User {
name: string;
email: string;
age: number;
}
namespace Validation {
export interface StringValidator {
isValid(s: string): boolean;
}
}
namespace Validation {
export class EmailValidator implements StringValidator {
isValid(s: string): boolean {
return /@/.test(s);
}
}
}
class User {
name: string;
}
namespace User {
export function createGuest(): User {
return { name: "Guest" };
}
}
const guest = User.createGuest();
类型合并规则
接口非函数成员必须唯一,函数成员重载
命名空间导出成员合并
类不能直接合并,但可以用mixin
枚举成员值可以覆盖
命名空间别名不能合并
interface A {
method(x: number): number;
}
interface A {
method(x: string): string; // 重载签名
}
interface B {
prop: string;
}
interface B {
prop: number; // 错误!类型冲突
}

10.6 模块增强(Module Augmentation)

Section titled “10.6 模块增强(Module Augmentation)”

扩展已有模块的功能:

// 原模块 express/index.d.ts
declare module "express" {
export interface Request {
user?: User;
}
}
// 扩展
// express-extensions.d.ts
import "express";
declare module "express" {
export interface Request {
sessionId?: string;
}
}
// 为express添加自定义属性
import "express";
declare module "express" {
export interface Request {
tenantId?: string;
traceId?: string;
}
}
// 为mongoose添加静态方法
import "mongoose";
declare module "mongoose" {
interface Model<T> {
findByAge(age: number): Promise<T[]>;
}
}
global-extensions.d.ts
// 扩展Array
declare global {
interface Array<T> {
first(): T | undefined;
last(): T | undefined;
}
}
Array.prototype.first = function() {
return this[0];
};
Array.prototype.last = function() {
return this[this.length - 1];
};
export {};

使用declare声明在非TypeScript代码中存在的符号:

// 环境变量
declare const process: {
env: {
NODE_ENV: string;
[key: string]: string;
};
};
// 全局函数
declare function setTimeout(callback: () => void, ms: number): number;
// 全局类
declare class CustomEvent {
detail: any;
constructor(type: string);
initCustomEvent(): void;
}

声明外部模块:

// 模块值
declare module "my-custom-module" {
export const VERSION: string;
export function doSomething(): void;
export class MyClass {}
}
// 默认导出
declare module "json" {
export default JSON.parse;
}

使用///引用类型:

/// <reference types="node" />
/// <reference path="./local.d.ts" />
// 使用示例
/// <reference types="express" />
// 全局可以使用Express类型
const app: Express = express();

utils.js
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
module.exports = { add, multiply };
utils.d.ts
export function add(a: number, b: number): number;
export function multiply(a: number, b: number): number;
// utils.ts(从utils.js复制并添加类型)
export function add(a: number, b: number): number {
return a + b;
}
export function multiply(a: number, b: number): number {
return a * b;
}
utils.js
/**
* @param {number} a
* @param {number} b
* @returns {number}
*/
function add(a, b) {
return a + b;
}

TypeScript会自动从JSDoc推断类型(需要配置allowJs: true)。

{
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"noEmit": true
}
}

本章学习了TypeScript的类型声明与声明文件:

  1. .d.ts声明文件:

    • 为JavaScript代码提供类型信息
    • 放在项目目录或node_modules/@types中
  2. 内置类型声明:

    • JavaScript标准库
    • DOM类型
    • 通过lib配置控制
  3. @types与DefinitelyTyped:

    • npm安装类型声明
    • 匹配主包版本
  4. 自定义类型声明:

    • declare module扩展外部模块
    • declare global扩展全局
  5. 声明合并:

    • 同名接口自动合并
    • 命名空间可以合并
  6. 模块增强:

    • import “module”后使用declare module扩展
    • 添加自定义属性和方法

为之前写的JavaScript函数创建对应的.d.ts声明文件。

安装@types/lodash,使用其类型声明。

为express的Request添加自定义属性。

创建两个同名的interface,验证合并行为。

将一个JavaScript模块迁移到TypeScript,使用JSDoc注释方式。



下一章我们将学习TypeScript的装饰器与元编程,掌握如何使用装饰器增强类和函数的行为。