模块系统让你组织代码,声明文件让你在 TypeScript 中使用 JavaScript 库。
7.1 ES 模块(ESM)
TypeScript 完全支持 ES Module 语法:
导出
// utils.ts
// 命名导出
export function add(a: number, b: number): number {
return a + b;
}
export const PI = 3.14159;
export interface User {
name: string;
age: number;
}
// 默认导出(每个模块只能有一个)
export default class Calculator {
add(a: number, b: number) { return a + b; }
}
导入
// main.ts
// 导入命名导出
import { add, PI, User } from "./utils";
// 导入默认导出
import Calculator from "./utils";
// 导入并重命名
import { add as sum } from "./utils";
// 导入所有
import * as Utils from "./utils";
Utils.add(1, 2);
Utils.PI;
// 只导入类型(编译后会被移除,不产生运行时代码)
import type { User } from "./utils";
// 混合导入
import Calculator, { add, type User } from "./utils";
重新导出
// index.ts —— 统一导出(桶文件 / barrel file)
export { add, PI } from "./math";
export { formatDate } from "./date";
export type { User, Config } from "./types";
// 重新导出所有
export * from "./math";
// 重新导出并重命名
export { add as sum } from "./math";
7.2 类型导入导出
TypeScript 提供了 type 关键字来标记纯类型导入/导出,确保编译后完全移除:
// types.ts
export interface User {
name: string;
age: number;
}
export type Status = "active" | "inactive";
// main.ts
// 方式一:import type(推荐)
import type { User, Status } from "./types";
// 方式二:内联 type
import { type User, type Status } from "./types";
// 方式三:混合值和类型
import { someFunction, type User } from "./module";
💡 使用
import type的好处:明确表明这是类型导入,编译后不会残留无用代码。
7.3 CommonJS 互操作
在 Node.js 项目中,可能需要与 CommonJS 模块交互:
// CommonJS 风格
const fs = require("fs"); // TS 中不推荐
// 推荐的 TS 写法
import fs from "fs"; // 需要 esModuleInterop: true
import * as fs from "fs"; // 不需要 esModuleInterop
// 导出 CommonJS 格式
export = function add(a: number, b: number) {
return a + b;
};
// 导入 CommonJS 格式
import add = require("./add");
7.4 声明文件(.d.ts)
声明文件只包含类型信息,不包含实现。它让 TypeScript 理解 JavaScript 代码的类型。
为什么需要声明文件?
// 假设你使用了一个纯 JS 库 my-lib.js
// TypeScript 不知道它的类型,会报错:
import { doSomething } from "my-lib"; // ❌ 找不到类型声明
// 解决方案:创建声明文件 my-lib.d.ts
编写声明文件
// my-lib.d.ts
// 声明模块
declare module "my-lib" {
export function doSomething(input: string): number;
export function doOther(items: string[]): boolean;
export interface Config {
debug: boolean;
timeout: number;
}
export default class MyLib {
constructor(config: Config);
run(): void;
}
}
全局声明
// global.d.ts
// 声明全局变量(比如通过 CDN 引入的库)
declare const jQuery: (selector: string) => any;
declare const $: typeof jQuery;
// 声明全局函数
declare function gtag(command: string, ...args: any[]): void;
// 扩展全局对象
declare global {
interface Window {
__APP_CONFIG__: {
apiUrl: string;
version: string;
};
}
}
// 声明全局类型
declare type Nullable<T> = T | null;
为 JSON / CSS / 图片等文件声明类型
// declarations.d.ts
// JSON 文件
declare module "*.json" {
const value: any;
export default value;
}
// CSS Modules
declare module "*.module.css" {
const classes: Record<string, string>;
export default classes;
}
declare module "*.module.scss" {
const classes: Record<string, string>;
export default classes;
}
// 图片文件
declare module "*.png" {
const src: string;
export default src;
}
declare module "*.svg" {
const src: string;
export default src;
}
// Vue 单文件组件
declare module "*.vue" {
import type { DefineComponent } from "vue";
const component: DefineComponent<{}, {}, any>;
export default component;
}
7.5 @types 和 DefinitelyTyped
大多数流行的 JS 库已经有社区维护的类型声明包:
# 安装类型声明
npm install --save-dev @types/lodash
npm install --save-dev @types/node
npm install --save-dev @types/express
npm install --save-dev @types/react
# 之后就能获得完整的类型提示
import _ from "lodash";
_.chunk([1, 2, 3, 4], 2); // ✅ 有完整的类型提示
查看是否需要 @types
- 库自带类型声明(package.json 中有
types字段)→ 不需要 - 需要单独安装
@types/xxx→ 去 npmjs.com 搜索 - 都没有 → 自己写声明文件
7.6 三斜线指令
用于引入其他声明文件(在现代项目中较少使用):
/// <reference types="node" />
/// <reference path="./custom.d.ts" />
💡 现代项目通常通过
tsconfig.json的types字段来管理,很少直接用三斜线指令。
7.7 命名空间(Namespace)
命名空间是 TypeScript 早期的模块化方案,现在主要用于声明文件中组织类型:
// 在声明文件中常见
declare namespace Express {
interface Request {
user?: {
id: string;
name: string;
};
}
}
// 也用于组织大量类型
namespace Validation {
export interface StringValidator {
isValid(s: string): boolean;
}
export class EmailValidator implements StringValidator {
isValid(s: string): boolean {
return s.includes("@");
}
}
}
const validator: Validation.StringValidator = new Validation.EmailValidator();
💡 新项目中不推荐使用命名空间来组织代码,应使用 ES Module。命名空间主要用在声明文件中。
7.8 模块解析策略
TypeScript 有多种模块解析策略,由 tsconfig.json 中的 moduleResolution 控制:
{
"compilerOptions": {
"moduleResolution": "bundler" // 推荐:适用于 Vite、webpack 等
// 其他选项:
// "node" - Node.js 传统解析
// "node16" - Node.js 16+ ESM 解析
// "nodenext" - 最新 Node.js 解析
}
}
路径别名
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@utils/*": ["src/utils/*"]
}
}
}
// 使用路径别名
import { Button } from "@components/Button";
import { formatDate } from "@utils/date";
⚠️ 路径别名只在 TypeScript 编译时生效,打包工具(Vite/webpack)也需要对应配置。
📝 练习
- 创建一个模块
math.ts,导出add、subtract、multiply、divide函数和一个MathOperation类型 - 创建
index.ts作为桶文件,重新导出所有内容 - 为一个假想的
analytics库写声明文件 - 声明全局的
__DEV__变量(boolean 类型)
// 参考答案
// 1. math.ts
export type MathOperation = (a: number, b: number) => number;
export const add: MathOperation = (a, b) => a + b;
export const subtract: MathOperation = (a, b) => a - b;
export const multiply: MathOperation = (a, b) => a * b;
export const divide: MathOperation = (a, b) => {
if (b === 0) throw new Error("除数不能为 0");
return a / b;
};
// 2. index.ts
export { add, subtract, multiply, divide } from "./math";
export type { MathOperation } from "./math";
// 3. analytics.d.ts
declare module "analytics" {
interface AnalyticsConfig {
trackingId: string;
debug?: boolean;
}
export function init(config: AnalyticsConfig): void;
export function track(event: string, data?: Record<string, any>): void;
export function identify(userId: string): void;
}
// 4. global.d.ts
declare const __DEV__: boolean;