Skip to content

第7章 模块系统

本章定位:学习TypeScript的模块系统,包括模块的基本概念、导入导出语法、模块解析策略,以及与Python import系统的对比。


TypeScript使用ES Modules(ESM)作为标准模块系统。每个.ts文件就是一个模块。模块内定义的变量、函数、类默认是私有的,需要显式导出才能被其他模块使用。

导出变量和常量:

// 导出单个
export const APP_NAME = "MyApp";
export const VERSION = "1.0.0";
// 先定义再导出
const MAX_SIZE = 100;
export { MAX_SIZE };
// 重命名导出
const MAX_USERS = 50;
export { MAX_USERS as MAX_USER_COUNT };
// 导出类型
export type UserId = string | number;
export interface User {
name: string;
age: number;
}

导出函数:

// 直接导出函数
export function add(a: number, b: number): number {
return a + b;
}
// 先定义再导出
function multiply(a: number, b: number): number {
return a * b;
}
export { multiply };
// 重命名导出
function divide(a: number, b: number): number {
return a / b;
}
export { divide as div };

导出类:

export class User {
constructor(
public name: string,
public age: number
) {}
greet(): string {
return `Hello, I'm ${this.name}`;
}
}
// 导出接口
export interface Config {
host: string;
port: number;
}

导入导出成员:

// 导入命名的导出
import { add, multiply } from "./math";
console.log(add(1, 2)); // 3
console.log(multiply(3, 4)); // 12
// 重命名导入
import { add as sum, multiply as times } from "./math";
// 导入所有命名导出
import * as MathUtils from "./math";
console.log(MathUtils.add(1, 2));
console.log(MathUtils.multiply(3, 4));

导入默认值导出:

// 假设 utils.ts 导出了默认函数
// export default function() { ... }
// 导入默认导出
import myFunction from "./utils";
// 混合导入(默认 + 命名)
import defaultExport, { named1, named2 } from "./module";
// 重新导出
export { add, multiply } from "./math";
export { User } from "./user";
// 重新导出并重命名
export { add as sum } from "./math";
// 重新导出所有
export * from "./math";
export type { User, Product } from "./models";

每个模块只能有一个默认导出:

math.ts
export default function(a: number, b: number): number {
return a + b;
}
// 或者
const calculator = {
add: (a: number, b: number) => a + b,
subtract: (a: number, b: number) => a - b
};
export default calculator;
// 导入(可以任意命名)
import calc from "./math";
calc.add(1, 2);

适合默认导出的场景:

  • 每个文件只导出一个主要功能
  • 类(但要谨慎,因为类通常有多个实例化方式)
  • 整个模块的入口

可以有多个命名导出:

operations.ts
export const PI = 3.14159;
export const E = 2.71828;
export function add(a: number, b: number): number {
return a + b;
}
export function subtract(a: number, b: number): number {
return a - b;
}
export class Calculator {
add(a: number, b: number): number {
return a + b;
}
}
// 导入
import { PI, E, add, subtract, Calculator } from "./operations";
特性默认导出命名导出
数量每个模块一个每个模块多个
导入名称可任意命名必须使用原名或重命名
重导出需指定名称简单
常用场景工具函数、主类常量、工具函数、多个类
main.ts
const helper = {
format: (s: string) => s.trim()
};
export default helper;
export const VERSION = "1.0.0";
export function greet(name: string): string {
return `Hello, ${name}!`;
}
// 导入
import helper, { VERSION, greet } from "./main";
helper.format(" hi ");
console.log(VERSION);
console.log(greet("Alice"));
// 当模块同时有默认导出和命名导出时
// 导入方式
import defaultExport, { named1, named2 } from "./module";
// 或者分别导入
import * as module from "./module";
module.default;
module.named1;

相对导入:

// 从当前目录导入
import { User } from "./user";
import { Config } from "./config";
// 从上级目录导入
import { Helper } from "../utils/helper";
// 从上两级目录导入
import { Logger } from "../../lib/logger";

非相对导入:

// 从node_modules导入
import express from "express";
import { Component } from "react";
// 从@scope导入
import { Button } from "@mui/material";

TypeScript使用两种模块解析策略:

Classic策略(默认,用于module: commonjs以外的情况):

导入 "module/name"
1. 从当前目录向上查找 node_modules/module/name.ts
2. 继续向上查找直到根目录

Node策略(用于module: commonjs或moduleResolution: node):

导入 "module/name"
1. 如果是".js"或".json",直接解析
2. 查找 node_modules/module/package.json 的 "main" 字段
3. 查找 node_modules/module/index.js
4. 向上级目录继续查找
{
"compilerOptions": {
"module": "commonjs", // 输出模块格式
"moduleResolution": "node", // 解析策略
"baseUrl": "./src", // 基础路径
"paths": {
"@utils/*": ["utils/*"],
"@components/*": ["components/*"]
},
"rootDirs": ["src", "generated"] // 多个源目录
}
}
// tsconfig.json
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@utils/*": ["utils/*"],
"@components/*": ["components/*"]
}
}
}
// 使用
import { formatDate } from "@utils/date";
import { Button } from "@components/Button";
// TypeScript编译器会根据以下顺序查找
// 1. .ts
// 2. .tsx
// 3. .d.ts
// 对于JavaScript文件
// 1. .js
// 2. .json
// 明确扩展名
import { User } from "./user.ts";

命名空间是TypeScript提供的代码组织方式,用于将相关代码分组:

namespace Validation {
export interface StringValidator {
isValid(s: string): boolean;
}
export class EmailValidator implements StringValidator {
isValid(s: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s);
}
}
export class PhoneValidator implements StringValidator {
isValid(s: string): boolean {
return /^\d{3}-\d{4}-\d{4}$/.test(s);
}
}
}
// 使用
const validators: Validation.StringValidator[] = [
new Validation.EmailValidator(),
new Validation.PhoneValidator()
];

可以跨文件分割命名空间:

validation.ts
namespace Validation {
export interface Validator {}
}
// email-validator.ts
/// <reference path="validation.ts" />
namespace Validation {
export class EmailValidator implements Validator {}
}
// phone-validator.ts
/// <reference path="validation.ts" />
namespace Validation {
export class PhoneValidator implements Validator {}
}
特性命名空间模块
文件组织跨文件分组一个文件一个模块
编译输出单一对象符合ES模块标准
导入方式全局访问import语句
推荐场景全局类型、声明应用代码

现代TypeScript推荐:使用模块(ES Modules)而非命名空间。

user/types.ts
// 推荐:使用模块
export interface User {
name: string;
age: number;
}
// user/service.ts
import { User } from "./types";
export function createUser(name: string, age: number): User {
return { name, age };
}

Python import:

# 导入整个模块
import os
# 导入指定成员
from math import pi, e
# 重命名
import pandas as pd
from collections import OrderedDict as OD
# 相对导入
from . import utils
from ..package import module

TypeScript import:

// 导入整个模块
import * as os from "os";
// 导入命名的导出
import { pi, e } from "math";
// 重命名
import { DataFrame as DF } from "pandas";
// 默认导入
import React from "react";
// 相对导入
import { utils } from "./utils";
import { module } from "../package/module";

Python:

# 标准库
import json
from collections import OrderedDict
# 第三方库
import numpy as np
from django.http import JsonResponse
# 本地模块
from .models import User
from ..utils import helper

TypeScript:

// 标准库(JavaScript内置)
import { promises as fs } from "fs";
import path from "path";
// 第三方库(DefinitelyTyped类型定义)
import express from "express";
import { Request, Response } from "express";
// 本地模块
import { User } from "./models";
import { helper } from "../utils";

Python:

my_module.py
# 直接在模块中定义
def func():
pass
class MyClass:
pass
# __all__ 控制导出
__all__ = ["func", "MyClass"]

TypeScript:

my-module.ts
export function func() {}
export class MyClass {}
// 重新导出
export { func as anotherName } from "./other";

Python:

from typing import List, Optional
def process(items: List[int]) -> Optional[str]:
pass
# 类型检查由mypy等工具完成

TypeScript:

import { List, Optional } from "typescript";
function process(items: number[]): string | undefined {
return items.length > 0 ? "ok" : undefined;
}
// 编译时类型检查

Python:

utils.py
# 每个模块只能有一个默认导出
class Utils:
pass
default_app = Utils()
# main.py
from utils import default_app as app

TypeScript:

utils.ts
class Utils {
// ...
}
export default new Utils();
// main.ts
import app from "./utils";

Python:

a.py
from b import B
class A:
pass
# b.py
from a import A
class B:
pass
# Python会报ImportError

TypeScript:

a.ts
import { B } from "./b";
export class A {
b: B;
constructor() {
this.b = new B();
}
}
// b.ts
import { A } from "./a";
export class B {
// ...
}
// TypeScript需要通过接口或延迟绑定解决

用于声明文件之间的依赖:

/// <reference path="./logger.ts" />
/// <reference types="node" />

常见用途:

// 1. 引用同目录下其他文件
/// <reference path="./base.ts" />
// 2. 引用node_modules类型
/// <reference types="express" />
// 3. 声明全局变量(使用const、///、declare)
/// <reference types="google.maps" />

用于为JavaScript代码提供类型信息:

global.d.ts
// 声明全局变量
declare const APP_VERSION: string;
declare const API_BASE_URL: string;
// 声明全局函数
declare function formatDate(date: Date): string;
// 声明模块
declare module "my-library" {
export function doSomething(): void;
export class MyClass {
constructor(value: string);
getValue(): string;
}
}

安装带有类型的库:

Terminal window
npm install @types/express
npm install @types/node

如果没有类型声明,可以创建本地声明:

types/my-untyped-lib.d.ts
declare module "my-untyped-lib" {
export function process(data: string): string;
export class Processor {
constructor(options?: object);
run(): void;
}
}
// 原始JavaScript
function greet(name) {
return "Hello, " + name;
}
// 1. 创建类型声明文件
// greet.d.ts
declare function greet(name: string): string;
// 2. 或者在代码中添加JSDoc注释
/**
* @param {string} name
* @returns {string}
*/
function greet(name) {
return "Hello, " + name;
}
// 3. 或者直接使用TypeScript
function greet(name: string): string {
return `Hello, ${name}`;
}

静态导入(在模块顶层):

import { helper } from "./helper";
// 编译时确定,不能在条件语句中使用

动态导入(按需加载):

// 动态导入模块
const modulePath = condition ? "./utils" : "./helpers";
import(modulePath).then(module => {
module.doSomething();
});
// async/await 方式
async function loadModule() {
const module = await import("./utils");
return module;
}

代码分割:

// 按需加载重型模块
async function loadDataAnalysis() {
const { DataAnalyzer } = await import("./data-analyzer");
return new DataAnalyzer();
}
// 用户点击按钮时才加载
button.addEventListener("click", async () => {
const { Chart } = await import("./chart");
const chart = new Chart(data);
chart.render();
});

条件加载:

const locale = getUserLocale();
const messages = await import(`./locales/${locale}.ts`);
messages.default.greeting;
// 动态导入返回Promise,类型会自动推断
async function loadConfig() {
const { config } = await import("./config");
return config;
}
// 或者显式指定类型
type ConfigModule = typeof import("./config");
async function loadConfig(): Promise<ConfigModule> {
return import("./config");
}

本章学习了TypeScript的模块系统:

  1. 导入导出基础:

    • 使用export导出,import导入
    • 支持命名导出和默认导出
    • 可以在导入时重命名
  2. 模块解析:

    • 相对导入(./、../)和非相对导入
    • Node.js风格解析策略
    • tsconfig.json配置路径别名
  3. 命名空间:

    • 使用namespace组织代码
    • 跨文件分割
    • 现代代码推荐使用模块替代
  4. Python对比:

    • 语法类似但符号不同(from vs { })
    • 都支持重命名和相对导入
    • Python用.,TypeScript用/
  5. 类型声明文件:

    • .d.ts为JavaScript提供类型信息
    • 使用declare声明类型
    • @types/*提供第三方库类型
  6. 动态导入:

    • import()按需加载
    • 支持代码分割和条件加载
    • 返回Promise

创建以下文件:

  • math/utils.ts:导出add、subtract、multiply函数
  • math/index.ts:重新导出所有函数
  • main.ts:导入并使用

创建一个config.ts,使用默认导出导出配置对象。

配置tsconfig.json,设置@utils/*别名,然后在代码中使用。

实现一个按需加载模块的功能,根据用户选择加载不同的计算器。

为无类型的npm包创建本地类型声明文件。



下一章我们将学习TypeScript的类型守卫与类型收缩,掌握如何在运行时安全地检查类型。