Skip to content

导入和导出

概述

导入(import)和导出(export)是 TypeScript 模块系统的核心机制。通过导出,我们可以将模块中的函数、类、类型、变量等暴露给其他模块使用;通过导入,我们可以从其他模块中获取需要的内容。掌握各种导入和导出语法,能够帮助我们更好地组织和管理代码。

导出(Export)

命名导出(Named Exports)

命名导出允许一个模块导出多个值,每个导出都有一个名称。这是最常用的导出方式。

基本语法

typescript
// 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 关键字统一导出:

typescript
// 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 允许导出类型、接口和类型别名:

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 语法。默认导出在导入时不需要使用花括号。

typescript
// logger.ts
class Logger {
  log(message: string): void {
    console.log(`[LOG] ${message}`);
  }
}

// 默认导出
export default Logger;
typescript
// 也可以直接导出值
export default function createLogger() {
  return new Logger();
}

// 或者导出对象
export default {
  version: '1.0.0',
  name: 'MyLogger'
};

混合导出

一个模块可以同时使用命名导出和默认导出:

typescript
// 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)

命名导入

使用花括号 {} 导入命名导出的内容:

typescript
// 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 关键字在导入时重命名:

typescript
// 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 语法导入模块的所有命名导出:

typescript
// 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 时,导入的是一个命名空间对象,所有导出的内容都作为该对象的属性。

默认导入

默认导入不需要使用花括号,可以自定义导入名称:

typescript
// app.ts
import Logger from './logger';
import MyLogger from './logger'; // 可以使用任意名称

const logger = new Logger();
logger.log('Hello, TypeScript!');

混合导入

可以同时导入默认导出和命名导出:

typescript
// app.ts
import ApiClient, { getUsers, getUserById } from './api';

const client = new ApiClient();
const users = getUsers();
const user = getUserById(1);

导入默认导出并重命名

typescript
// app.ts
import { default as Api, getUsers } from './api';

const client = new Api();

类型导入

TypeScript 提供了 import type 语法,专门用于导入类型。这些导入在编译后会被移除,有助于代码优化和 tree-shaking。

typescript
// 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 修饰符:

typescript
// app.ts
import { type User, type Status, UserService } from './services';

// 这样既导入了类型,也导入了值
const service = new UserService();
const user: User = service.getUser(1);

重新导出(Re-export)

重新导出允许一个模块导出另一个模块的内容,这对于创建统一的模块入口非常有用。

基本重新导出

typescript
// math/index.ts
export { add, subtract } from './operations';
export { multiply, divide } from './calculations';
export { PI, E } from './constants';

重新导出并重命名

typescript
// utils/index.ts
export { formatDate as date, formatCurrency as currency } from './formatters';
export { validateEmail as email, validatePhone as phone } from './validators';

重新导出所有内容

使用 export * 重新导出另一个模块的所有命名导出:

typescript
// math/index.ts
export * from './operations';
export * from './calculations';
export * from './constants';

注意

export * 不会重新导出默认导出,也不会重新导出同名的导出(后面的会覆盖前面的)。

重新导出默认导出

typescript
// components/index.ts
export { default as Button } from './Button';
export { default as Input } from './Input';
export { default as Modal } from './Modal';

重新导出类型

typescript
// types/index.ts
export type { User, Product, Order } from './models';
export type { ApiResponse, ApiError } from './api';

动态导入(Dynamic Import)

动态导入允许在运行时按需加载模块,返回一个 Promise。

typescript
// 动态导入
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:创建工具模块库

typescript
// 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';
typescript
// 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:类型和值的分离导出

typescript
// 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();
typescript
// 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:条件导入和动态加载

typescript
// 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 创建统一入口

typescript
// 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';
typescript
// app.tsx - 从统一入口导入
import { Button, Input, type ButtonProps, type InputProps } from './components';

2. 类型和值的分离

typescript
// 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. 避免循环依赖

typescript
// ❌ 错误:循环依赖
// 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();
}

注意事项

注意

  1. 默认导出的限制:每个模块只能有一个默认导出,但可以有多个命名导出。

  2. 导出名称冲突:使用 export * 时,如果多个模块导出了同名的内容,后面的导出会覆盖前面的。

  3. 类型导入优化:使用 import type 导入类型可以确保这些导入在编译后被移除,有助于减小打包体积。

  4. 循环依赖:避免模块之间的循环依赖,这可能导致运行时错误或未定义的行为。

  5. 路径解析:确保导入路径正确,TypeScript 会根据 tsconfig.json 中的配置解析模块路径。

最佳实践

  • 优先使用命名导出:命名导出更加明确,支持 tree-shaking,且 IDE 支持更好
  • 使用 import type 导入类型:明确区分类型导入和值导入,提高代码清晰度
  • 创建统一的模块入口:使用 index.ts 文件重新导出,提供清晰的模块接口
  • 避免使用 export *:明确列出需要导出的内容,避免意外的名称冲突
  • 合理使用默认导出:默认导出适合模块的主要功能,如主要的类或函数

相关链接

基于 VitePress 构建