TypeScript 模块
大白话解释: TypeScript 的模块就像"文件柜"。每个文件是一个抽屉,抽屉里可以放东西(导出),也可以从别的抽屉拿东西(导入)。这样代码就不会乱成一团,每个文件负责一件事。
为什么要用模块?
- 代码组织:把代码分成多个文件,每个文件负责一件事
- 避免冲突:不同文件的变量不会互相污染
- 按需加载:只导入需要的代码,减少打包体积
- 团队协作:不同人可以同时开发不同模块
TypeScript 支持 ES 模块和 CommonJS 模块系统,提供了强大的模块化编程能力。
ES 模块
基本导入导出
ts
// ---- ES 模块:现代 JavaScript 的模块系统 ----
// 大白话:就像"文件柜",通过 import/export 打开抽屉拿东西
// 导出:把东西从抽屉里拿出来
export interface User {
id: number
name: string
email: string
}
export function createUser(name: string, email: string): User {
return { id: Date.now(), name, email }
}
export class UserService {
private users: User[] = []
add(user: User): void {
this.users.push(user)
}
findById(id: number): User | undefined {
return this.users.find(user => user.id === id)
}
}
// 默认导出:一个文件只能有一个
export default class App {
// ...
}ts
// 导入:从别的抽屉拿东西进来
import App, { User, createUser, UserService } from './user'
// 导入全部
import * as UserModule from './user'
const user = UserModule.createUser('张三', '[email protected]')
// 重命名导入
import { createUser as createNewUser } from './user'
// 仅导入类型(编译后会被删除,不会增加打包体积)
import type { User } from './user'重新导出
ts
// ---- 重新导出:把导入的东西再导出去 ----
// 大白话:就像"转发",从 A 拿来,再给 B
// 重新导出指定内容
export { User, createUser } from './user'
export { UserService } from './user'
// 重新导出全部
export * from './user'
// 重命名重新导出
export { createUser as createNewUser } from './user'
// 默认导出重新导出
export { default as App } from './App'
// 实际用法:创建统一的导出入口
// services/index.ts
export { UserService } from './UserService'
export { OrderService } from './OrderService'
export { PaymentService } from './PaymentService'
// 使用时只需从一个地方导入
import { UserService, OrderService, PaymentService } from '@/services'CommonJS 模块
基本语法
ts
// ---- CommonJS:Node.js 的模块系统 ----
// 大白话:就像"快递",用 require 取件,用 module.exports 寄件
// 注意:这是旧版语法,现在推荐用 ES 模块(import/export)
// 什么时候会遇到?维护老项目、写 Node.js 脚本时可能碰到
// CommonJS 导出(旧语法,用 export = 而不是 export default)
export = UserService
class UserService {
private users: User[] = []
add(user: User): void {
this.users.push(user)
}
}
// CommonJS 导入(旧语法,用 import = require 而不是 import from)
import UserService = require('./UserService')
const service = new UserService()兼容性配置
json
// tsconfig.json
{
"compilerOptions": {
"module": "CommonJS",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
}
}模块解析
相对路径导入
ts
// ---- 相对路径导入:从当前文件出发 ----
// 大白话:就像"问路",从当前位置出发,走到目标位置
// 同级目录
import { User } from './User'
// 上级目录
import { UserService } from '../services/UserService'
// 上上级目录
import { Config } from '../../config'
// 导入 JSON 文件
import config from '../config.json'非相对路径导入
ts
// ---- 非相对路径导入:从 node_modules ----
// 大白话:就像"网购",不用知道仓库在哪,直接说商品名
// 导入第三方库
import express from 'express'
import { Request, Response } from 'express'
import lodash from 'lodash'
// 导入类型定义
import type { Express } from 'express'路径别名
ts
// ---- 路径别名:给路径起个短名字 ----
// 大白话:就像"快捷方式",不用写长长的路径
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@utils/*": ["src/utils/*"]
}
}
}
// 使用
import { Button } from '@components/Button'
import { formatDate } from '@utils/date'
import { User } from '@/models/User'
// Vite 配置
// vite.config.ts
import { defineConfig } from 'vite'
import path from 'path'
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components'),
'@utils': path.resolve(__dirname, 'src/utils'),
},
},
})声明文件
全局声明
ts
// ---- 全局声明:给全局变量加类型 ----
// 大白话:就像"给窗户贴膜",让外面的变量也能被 TS 识别
// global.d.ts
declare global {
interface Window {
__APP_CONFIG__: {
apiUrl: string
env: 'development' | 'production'
}
}
// 全局类型
type Nullable<T> = T | null
type Optional<T> = T | undefined
}
export {}模块声明
ts
// ---- 模块声明:给没有类型的库加类型 ----
// 大白话:就像"翻译",让 TS 能理解没有类型的库
// types/express.d.ts
declare module 'express' {
export interface Request {
body: any
params: Record<string, string>
query: Record<string, string>
}
export interface Response {
json(data: any): void
status(code: number): Response
}
export interface Application {
get(path: string, handler: (req: Request, res: Response) => void): void
post(path: string, handler: (req: Request, res: Response) => void): void
listen(port: number, callback?: () => void): void
}
export default function express(): Application
}第三方库声明
ts
// ---- 第三方库声明:给没有类型的库加类型 ----
// 大白话:就像"给没有标签的瓶子贴标签"
// types/lodash.d.ts
declare module 'lodash' {
export function debounce<T extends (...args: any[]) => any>(
func: T,
wait?: number
): T
export function throttle<T extends (...args: any[]) => any>(
func: T,
wait?: number
): T
export function cloneDeep<T>(value: T): T
export function merge(...objects: any[]): any
}
// 使用
import { debounce, cloneDeep } from 'lodash'命名空间
基本命名空间
ts
// ---- 命名空间:旧式的模块系统 ----
// 大白话:就像"文件夹",把相关的类型放在一起
// 注意:现在推荐用 ES 模块,命名空间主要用于声明文件
namespace Validation {
export interface Validator {
validate(value: string): boolean
}
export class EmailValidator implements Validator {
validate(value: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
}
}
export class PhoneValidator implements Validator {
validate(value: string): boolean {
return /^1[3-9]\d{9}$/.test(value)
}
}
}
// 使用
const emailValidator = new Validation.EmailValidator()
const phoneValidator = new Validation.PhoneValidator()
emailValidator.validate('[email protected]') // true
phoneValidator.validate('13800138000') // true命名空间合并
ts
// ---- 命名空间合并:多个地方定义同一个命名空间 ----
// 大白话:就像"拼图",把多个部分拼在一起
namespace Validation {
export function isEmpty(value: string): boolean {
return value.trim().length === 0
}
}
// 合并后可以使用
Validation.isEmpty('') // true
Validation.EmailValidator // 也可以用模块导出策略
默认导出 vs 命名导出
ts
// ---- 默认导出:一个文件一个主要导出 ----
// 大白话:就像"主角",一个文件只能有一个主角
// user.ts
export default class User {
constructor(public name: string) {}
}
// 导入时可以任意命名
import User from './user'
import MyUser from './user' // 也可以叫别的名字
// ---- 命名导出:一个文件可以有多个导出 ----
// 大白话:就像"配角",一个文件可以有多个配角
// user.ts
export class User {
constructor(public name: string) {}
}
export interface UserConfig {
name: string
age: number
}
export function createUser(config: UserConfig): User {
return new User(config.name)
}
// 导入时必须用原名
import { User, UserConfig, createUser } from './user'选择建议
| 场景 | 推荐 | 原因 |
|---|---|---|
| 单个主要导出 | 默认导出 | 导入时可以任意命名 |
| 多个导出 | 命名导出 | 明确知道导入什么 |
| 工具函数库 | 命名导出 | 按需导入,支持 tree-shaking |
| 类库 | 默认导出 | 通常一个文件一个主要类 |
| 类型定义 | 命名导出 | 类型通常需要明确导入 |
模块加载策略
静态导入
ts
// ---- 静态导入:编译时确定依赖 ----
// 大白话:就像"提前预约",编译时就确定要什么
import { User } from './user'
import { UserService } from './UserService'
// 优点:
// - 编译时类型检查
// - 支持 tree-shaking
// - 依赖关系明确动态导入
ts
// ---- 动态导入:运行时加载 ----
// 大白话:就像"临时抱佛脚",需要的时候才去拿
async function loadUserModule() {
const { User } = await import('./user')
return new User('张三')
}
// 代码分割:按需加载组件(Vue 3 路由懒加载)
const routes = [
{
path: '/home',
component: () => import('./views/Home.vue'), // 动态导入,打包时会自动分割
},
{
path: '/about',
component: () => import('./views/About.vue'),
},
{
path: '/heavy',
// defineAsyncComponent:Vue 3 的异步组件,支持加载状态和错误处理
component: defineAsyncComponent({
loader: () => import('./views/HeavyComponent.vue'),
loadingComponent: LoadingSpinner, // 加载中显示的组件
errorComponent: ErrorDisplay, // 加载失败显示的组件
delay: 200, // 延迟多久显示 loading(防止闪烁)
timeout: 10000, // 超时时间
}),
},
]
// 条件加载:只在开发环境加载
if (import.meta.env.DEV) {
const { DevTools } = await import('./DevTools')
DevTools.init()
}模块配置
tsconfig.json 模块配置
json
{
"compilerOptions": {
// 模块系统:决定 import/export 编译成什么格式
// "ESNext":现代浏览器/Vite 项目用这个,保持 import/export 原样
// "CommonJS":Node.js 旧项目用,编译成 require/module.exports
// "AMD"/"UMD":旧版浏览器兼容方案,现在很少用
"module": "ESNext",
// 模块解析策略:决定 import './foo' 怎么找到文件
// "node":Node.js 风格,先找 foo.ts,再找 foo/index.ts,最后找 node_modules
// "bundler":Vite/Webpack 项目推荐,比 node 更宽松,支持路径别名
// "node16"/"nodenext":新版 Node.js,强制写扩展名
"moduleResolution": "bundler",
// 允许导入 JSON 文件:import config from './config.json'
"resolveJsonModule": true,
// 生成 .d.ts 声明文件:让其他项目能用你的类型
"declaration": true,
// 允许合成默认导入:import React from 'react'(即使 react 没有 default export)
"allowSyntheticDefaultImports": true,
// ES 模块互操作:让 CommonJS 模块可以 default import
// 比如 import express from 'express'(express 是 CommonJS 模块)
"esModuleInterop": true,
// 强制文件名大小写一致:import './User' 不能匹配 user.ts
"forceConsistentCasingInFileNames": true
}
}Vite 配置
ts
// vite.config.ts
import { defineConfig } from 'vite'
import path from 'path'
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components'),
'@utils': path.resolve(__dirname, 'src/utils'),
},
},
})实际应用示例
工具函数库
ts
// ---- 工具函数库:把常用的函数放在一起 ----
// 大白话:就像"工具箱",需要什么工具就拿什么
// utils/date.ts
export function formatDate(date: Date, format: string): string {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return format
.replace('YYYY', String(year))
.replace('MM', month)
.replace('DD', day)
}
export function parseDate(dateString: string): Date {
return new Date(dateString)
}
export function isToday(date: Date): boolean {
const today = new Date()
return date.toDateString() === today.toDateString()
}
// utils/string.ts
export function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1)
}
export function camelCase(str: string): string {
return str.replace(/-([a-z])/g, (_, char) => char.toUpperCase())
}
export function kebabCase(str: string): string {
return str.replace(/[A-Z]/g, char => `-${char.toLowerCase()}`)
}
// utils/index.ts(统一导出入口)
export * from './date'
export * from './string'
// 使用
import { formatDate, capitalize } from '@/utils'API 服务模块
ts
// ---- API 服务模块:封装网络请求 ----
// 大白话:就像"快递公司",负责发送和接收请求
// services/api.ts
export class ApiService {
private baseUrl: string
constructor(baseUrl: string) {
this.baseUrl = baseUrl
}
async get<T>(endpoint: string): Promise<T> {
const response = await fetch(`${this.baseUrl}${endpoint}`)
return response.json()
}
async post<T>(endpoint: string, data: any): Promise<T> {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
return response.json()
}
}
// services/user.ts
import { ApiService } from './api'
export class UserService {
private api: ApiService
constructor(api: ApiService) {
this.api = api
}
async getUsers(): Promise<User[]> {
return this.api.get<User[]>('/users')
}
async createUser(user: Omit<User, 'id'>): Promise<User> {
return this.api.post<User>('/users', user)
}
}
// services/index.ts(统一导出入口)
export { ApiService } from './api'
export { UserService } from './user'
// 使用
import { ApiService, UserService } from '@/services'
const api = new ApiService('https://api.example.com')
const userService = new UserService(api)组件模块
vue
<!-- ---- 组件模块:把组件相关的东西放在一起 ---- -->
<!-- 大白话:就像"零件盒",每个盒子里装一个零件的所有配件 -->
<!-- components/Button/Button.vue -->
<script setup lang="ts">
// 定义 Props 类型
export interface ButtonProps {
variant?: 'primary' | 'secondary' | 'danger' // 按钮样式
size?: 'small' | 'medium' | 'large' // 按钮大小
disabled?: boolean // 是否禁用
}
// 带默认值的 Props
const props = withDefaults(defineProps<ButtonProps>(), {
variant: 'primary',
size: 'medium',
disabled: false,
})
// 定义事件类型
const emit = defineEmits<{
click: [event: MouseEvent]
}>()
</script>
<template>
<button
:class="['btn', `btn-${variant}`, `btn-${size}`]"
:disabled="disabled"
@click="emit('click', $event)"
>
<slot />
</button>
</template>ts
// components/Button/index.ts(统一导出入口)
export { default as Button } from './Button.vue'
export type { ButtonProps } from './Button.vue'
// 使用
import { Button } from '@/components/Button'
import type { ButtonProps } from '@/components/Button'常见问题
循环依赖
ts
// ---- 循环依赖:A 依赖 B,B 依赖 A ----
// 大白话:就像"鸡生蛋、蛋生鸡",互相依赖
// 问题:循环依赖
// a.ts
import { b } from './b'
export const a = 1
// b.ts
import { a } from './a'
export const b = 2
// 解决方案 1:提取公共模块,打破循环
// 将互相依赖的部分移到第三个模块
// types.ts
export interface UserData { id: number; name: string }
export interface OrderData { userId: number; product: string }
// a.ts
import type { OrderData } from './types'
export function createOrder(order: OrderData) { /* ... */ }
// b.ts
import type { UserData } from './types'
export function getUser(id: number): UserData { /* ... */ }
// 解决方案 2:延迟导入
// a.ts
export const a = 1
export function getB() {
return import('./b').then(m => m.b)
}模块类型定义缺失
ts
// ---- 第三方库没有类型定义 ----
// 大白话:就像"没有标签的瓶子",不知道里面是什么
// 问题:第三方库没有类型定义
import someLib from 'some-lib' // ❌ 类型错误
// 解决方案 1:安装类型定义(推荐)
// npm install --save-dev @types/some-lib
// 解决方案 2:创建声明文件
// types/some-lib.d.ts
declare module 'some-lib' {
export function someFunction(): void
export default someLib
}
// 解决方案 3:使用 any(不推荐)
declare module 'some-lib' {
const someLib: any
export default someLib
}导入路径问题
ts
// ---- 导入路径问题 ----
// 大白话:就像"找不到路",路径写错了
// 问题:路径解析失败
import { User } from './models/User' // ❌ 找不到模块
// 解决方案 1:检查文件扩展名
import { User } from './models/User.ts'
// 解决方案 2:配置路径别名
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
// vite.config.ts
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
})
// 使用
import { User } from '@/models/User'