导入和导出
概述
导入(import)和导出(export)是 TypeScript 模块系统的核心机制。通过导出,我们可以将模块中的函数、类、类型、变量等暴露给其他模块使用;通过导入,我们可以从其他模块中获取需要的内容。掌握各种导入和导出语法,能够帮助我们更好地组织和管理代码。
导出(Export)
命名导出(Named Exports)
命名导出允许一个模块导出多个值,每个导出都有一个名称。这是最常用的导出方式。
基本语法
// math.ts
export function add(a: number, b: number): number {
return a + b;
}
export function subtract(a: number, b: number): number {
return b - a;
}
export const PI = 3.14159;
export class Calculator {
multiply(a: number, b: number): number {
return a * b;
}
}导出列表
可以在文件末尾使用 export 关键字统一导出:
// utils.ts
function formatDate(date: Date): string {
return date.toISOString();
}
function formatCurrency(amount: number): string {
return `$${amount.toFixed(2)}`;
}
const DEFAULT_LOCALE = 'en-US';
// 统一导出
export { formatDate, formatCurrency, DEFAULT_LOCALE };
// 导出时重命名
export { formatDate as format, DEFAULT_LOCALE as LOCALE };导出类型和接口
TypeScript 允许导出类型、接口和类型别名:
// types.ts
export interface User {
id: number;
name: string;
email: string;
}
export type Status = 'active' | 'inactive' | 'pending';
export type UserWithStatus = User & { status: Status };
// 也可以使用 export type 明确导出类型
export type { User, Status };默认导出(Default Export)
每个模块只能有一个默认导出,使用 export default 语法。默认导出在导入时不需要使用花括号。
// logger.ts
class Logger {
log(message: string): void {
console.log(`[LOG] ${message}`);
}
}
// 默认导出
export default Logger;// 也可以直接导出值
export default function createLogger() {
return new Logger();
}
// 或者导出对象
export default {
version: '1.0.0',
name: 'MyLogger'
};混合导出
一个模块可以同时使用命名导出和默认导出:
// api.ts
// 命名导出
export function getUsers(): User[] {
return [];
}
export function getUserById(id: number): User | null {
return null;
}
// 默认导出
export default class ApiClient {
baseUrl: string = 'https://api.example.com';
}导入(Import)
命名导入
使用花括号 {} 导入命名导出的内容:
// app.ts
import { add, subtract, PI } from './math';
import type { Calculator } from './math';
const sum = add(5, 3); // 8
const diff = subtract(10, 4); // 6
console.log(PI); // 3.14159导入时重命名
可以使用 as 关键字在导入时重命名:
// app.ts
import { add as sum, subtract as diff } from './math';
import { formatDate as format } from './utils';
const result = sum(1, 2); // 3
const date = format(new Date());导入所有命名导出
使用 * as 语法导入模块的所有命名导出:
// app.ts
import * as MathUtils from './math';
const result1 = MathUtils.add(5, 3);
const result2 = MathUtils.subtract(10, 4);
console.log(MathUtils.PI);提示
使用 import * as 时,导入的是一个命名空间对象,所有导出的内容都作为该对象的属性。
默认导入
默认导入不需要使用花括号,可以自定义导入名称:
// app.ts
import Logger from './logger';
import MyLogger from './logger'; // 可以使用任意名称
const logger = new Logger();
logger.log('Hello, TypeScript!');混合导入
可以同时导入默认导出和命名导出:
// app.ts
import ApiClient, { getUsers, getUserById } from './api';
const client = new ApiClient();
const users = getUsers();
const user = getUserById(1);导入默认导出并重命名
// app.ts
import { default as Api, getUsers } from './api';
const client = new Api();类型导入
TypeScript 提供了 import type 语法,专门用于导入类型。这些导入在编译后会被移除,有助于代码优化和 tree-shaking。
// app.ts
// 只导入类型
import type { User, Status } from './types';
import type { UserWithStatus } from './types';
// 混合导入:类型和值
import { UserService, type User } from './services';
// 使用类型
const user: User = { id: 1, name: 'John', email: 'john@example.com' };
const status: Status = 'active';最佳实践
使用 import type 导入类型可以提高代码清晰度,并确保类型导入不会影响运行时代码。这对于构建工具进行 tree-shaking 优化很有帮助。
仅类型导入的语法
TypeScript 4.5+ 支持在导入语句中使用 type 修饰符:
// app.ts
import { type User, type Status, UserService } from './services';
// 这样既导入了类型,也导入了值
const service = new UserService();
const user: User = service.getUser(1);重新导出(Re-export)
重新导出允许一个模块导出另一个模块的内容,这对于创建统一的模块入口非常有用。
基本重新导出
// math/index.ts
export { add, subtract } from './operations';
export { multiply, divide } from './calculations';
export { PI, E } from './constants';重新导出并重命名
// utils/index.ts
export { formatDate as date, formatCurrency as currency } from './formatters';
export { validateEmail as email, validatePhone as phone } from './validators';重新导出所有内容
使用 export * 重新导出另一个模块的所有命名导出:
// math/index.ts
export * from './operations';
export * from './calculations';
export * from './constants';注意
export * 不会重新导出默认导出,也不会重新导出同名的导出(后面的会覆盖前面的)。
重新导出默认导出
// components/index.ts
export { default as Button } from './Button';
export { default as Input } from './Input';
export { default as Modal } from './Modal';重新导出类型
// types/index.ts
export type { User, Product, Order } from './models';
export type { ApiResponse, ApiError } from './api';动态导入(Dynamic Import)
动态导入允许在运行时按需加载模块,返回一个 Promise。
// 动态导入
async function loadModule() {
const math = await import('./math');
const result = math.add(5, 3);
console.log(result);
}
// 使用条件导入
if (condition) {
const utils = await import('./utils');
utils.doSomething();
}
// 类型安全的动态导入
async function loadUserModule() {
const { UserService, type User } = await import('./services');
const service = new UserService();
const user: User = service.getUser(1);
}提示
动态导入对于代码分割(code splitting)和按需加载非常有用,可以显著减少初始加载时间。
使用示例
示例 1:创建工具模块库
// utils/string.ts
export function capitalize(str: string): string {
if (!str) return str;
return str.charAt(0).toUpperCase() + str.slice(1);
}
export function reverse(str: string): string {
return str.split('').reverse().join('');
}
// utils/number.ts
export function formatNumber(num: number, decimals: number = 2): string {
return num.toFixed(decimals);
}
export function clamp(value: number, min: number, max: number): number {
return Math.min(Math.max(value, min), max);
}
// utils/index.ts - 统一入口
export * from './string';
export * from './number';
// 也可以导出默认值
export { default as StringUtils } from './string-default';// app.ts
import { capitalize, reverse, formatNumber, clamp } from './utils';
const name = capitalize('typescript'); // "TypeScript"
const reversed = reverse('hello'); // "olleh"
const formatted = formatNumber(3.14159, 2); // "3.14"
const clamped = clamp(150, 0, 100); // 100示例 2:类型和值的分离导出
// models/user.ts
export interface User {
id: number;
name: string;
email: string;
}
export type UserRole = 'admin' | 'user' | 'guest';
export class UserService {
private users: User[] = [];
createUser(user: Omit<User, 'id'>): User {
const newUser: User = {
id: this.users.length + 1,
...user
};
this.users.push(newUser);
return newUser;
}
getUser(id: number): User | undefined {
return this.users.find(u => u.id === id);
}
}
// 默认导出服务实例
export default new UserService();// app.ts
// 导入类型
import type { User, UserRole } from './models/user';
// 导入类和实例
import UserService, { UserService as UserServiceClass } from './models/user';
// 使用类型
const user: User = {
id: 1,
name: 'John',
email: 'john@example.com'
};
const role: UserRole = 'admin';
// 使用默认导出的实例
const foundUser = UserService.getUser(1);
// 或者创建新实例
const service = new UserServiceClass();
const newUser = service.createUser({ name: 'Alice', email: 'alice@example.com' });示例 3:条件导入和动态加载
// features/analytics.ts
export class Analytics {
track(event: string, data: any): void {
console.log(`Tracking: ${event}`, data);
}
}
// features/logger.ts
export class Logger {
log(message: string): void {
console.log(message);
}
}
// app.ts
async function initializeFeatures() {
// 根据环境动态加载功能模块
if (process.env.NODE_ENV === 'production') {
const { Analytics } = await import('./features/analytics');
const analytics = new Analytics();
analytics.track('app_started', { timestamp: Date.now() });
}
// 始终加载日志
const { Logger } = await import('./features/logger');
const logger = new Logger();
logger.log('Application started');
}
initializeFeatures();常见模式和最佳实践
1. 使用 index.ts 创建统一入口
// components/Button.tsx
export interface ButtonProps {
label: string;
onClick: () => void;
}
export default function Button(props: ButtonProps) {
return <button onClick={props.onClick}>{props.label}</button>;
}
// components/Input.tsx
export interface InputProps {
value: string;
onChange: (value: string) => void;
}
export default function Input(props: InputProps) {
return <input value={props.value} onChange={e => props.onChange(e.target.value)} />;
}
// components/index.ts - 统一导出
export { default as Button, type ButtonProps } from './Button';
export { default as Input, type InputProps } from './Input';// app.tsx - 从统一入口导入
import { Button, Input, type ButtonProps, type InputProps } from './components';2. 类型和值的分离
// types.ts - 只导出类型
export interface Config {
apiUrl: string;
timeout: number;
}
export type Theme = 'light' | 'dark';
// services.ts - 导出实现
import type { Config } from './types';
export class ApiService {
constructor(private config: Config) {}
// ...
}
// app.ts
import type { Config, Theme } from './types';
import { ApiService } from './services';3. 避免循环依赖
// ❌ 错误:循环依赖
// a.ts
import { funcB } from './b';
export function funcA() {
return funcB();
}
// b.ts
import { funcA } from './a';
export function funcB() {
return funcA();
}
// ✅ 正确:提取公共依赖
// common.ts
export function commonFunc() {
// ...
}
// a.ts
import { commonFunc } from './common';
export function funcA() {
return commonFunc();
}
// b.ts
import { commonFunc } from './common';
export function funcB() {
return commonFunc();
}注意事项
注意
默认导出的限制:每个模块只能有一个默认导出,但可以有多个命名导出。
导出名称冲突:使用
export *时,如果多个模块导出了同名的内容,后面的导出会覆盖前面的。类型导入优化:使用
import type导入类型可以确保这些导入在编译后被移除,有助于减小打包体积。循环依赖:避免模块之间的循环依赖,这可能导致运行时错误或未定义的行为。
路径解析:确保导入路径正确,TypeScript 会根据
tsconfig.json中的配置解析模块路径。
最佳实践
- 优先使用命名导出:命名导出更加明确,支持 tree-shaking,且 IDE 支持更好
- 使用
import type导入类型:明确区分类型导入和值导入,提高代码清晰度 - 创建统一的模块入口:使用
index.ts文件重新导出,提供清晰的模块接口 - 避免使用
export *:明确列出需要导出的内容,避免意外的名称冲突 - 合理使用默认导出:默认导出适合模块的主要功能,如主要的类或函数
相关链接
- 模块系统概述 - 了解模块系统的基本概念
- 模块解析 - 深入了解模块解析策略
- 命名空间 - 了解 TypeScript 命名空间
- TypeScript 官方文档 - 模块