TypeScript中的import语法详解
1 TypeScript模块系统概述
1.1 什么是模块化开发
模块化开发是现代前端开发的核心概念之一。它将复杂的代码拆分成独立的、可重用的模块,每个模块都有明确的职责和接口。
// user.service.ts - 一个简单的用户服务模块
export class UserService {getUsers(): Promise<User[]> {return fetch('/api/users').then(res => res.json());}getUserById(id: number): Promise<User> {return fetch(`/api/users/${id}`).then(res => res.json());}
}export interface User {id: number;name: string;email: string;
}
模块化的好处包括:
- 可维护性:代码结构清晰,易于维护
- 可重用性:模块可以在不同项目中重复使用
- 依赖管理:明确模块间的依赖关系
- 作用域隔离:避免全局变量污染
1.2 TypeScript模块系统的特点
TypeScript完全兼容ES6模块系统,并在此基础上增加了类型检查和更丰富的功能。
// math.utils.ts
export const PI = 3.14159;export function add(a: number, b: number): number {return a + b;
}export function multiply(a: number, b: number): number {return a * b;
}// 默认导出
export default class Calculator {calculate(operation: 'add' | 'multiply', a: number, b: number): number {switch(operation) {case 'add': return add(a, b);case 'multiply': return multiply(a, b);default: throw new Error('Unsupported operation');}}
}
TypeScript模块系统的主要特点:
- 静态分析:编译时就能确定模块依赖关系
- 类型安全:导入导出都有完整的类型检查
- 工具支持:IDE可以提供智能提示和自动导入
- Tree Shaking:未使用的代码可以被构建工具移除
1.3 模块与命名空间的区别
在TypeScript早期版本中,命名空间(namespace)是组织代码的主要方式,但现在推荐使用模块。
// 使用命名空间(不推荐)
namespace Validation {export interface StringValidator {isAcceptable(s: string): boolean;}const lettersRegexp = /^[A-Za-z]+$/;export class LettersOnlyValidator implements StringValidator {isAcceptable(s: string) {return lettersRegexp.test(s);}}
}// 使用模块(推荐)
// validation.ts
export interface StringValidator {isAcceptable(s: string): boolean;
}const lettersRegexp = /^[A-Za-z]+$/;export class LettersOnlyValidator implements StringValidator {isAcceptable(s: string): boolean {return lettersRegexp.test(s);}
}
模块与命名空间的主要区别:
特性 | 模块 | 命名空间 |
---|---|---|
作用域 | 文件级别 | 全局级别 |
加载方式 | 按需加载 | 全部加载 |
依赖管理 | 明确声明 | 隐式依赖 |
构建优化 | 支持Tree Shaking | 不支持 |
2 import语法基础
2.1 import语句的基本概念
import
语句用于从其他模块中引入变量、函数、类或其他可导出的值。它是ES6模块系统的核心语法之一。
// 基本语法结构
// import [导入声明] from '[模块路径]';// 从math.utils.ts模块导入
import Calculator, { PI, add, multiply } from './math.utils';
import { PI as PI_CONSTANT } from './math.utils'; // 重命名导入
import * as MathUtils from './math.utils'; // 命名空间导入
import './side-effect-module'; // 仅导入副作用
2.2 默认导入(Default Import)
每个模块可以有一个默认导出,使用默认导入时不需要花括号。
// logger.ts
export default class Logger {log(message: string): void {console.log(`[LOG] ${message}`);}error(message: string): void {console.error(`[ERROR] ${message}`);}
}// config.ts
const config = {apiUrl: 'https://api.example.com',timeout: 5000
};export default config;// app.ts - 使用默认导入
import Logger from './logger'; // 导入默认类
import config from './config'; // 导入默认对象const logger = new Logger();
logger.log('Application started');console.log(`API URL: ${config.apiUrl}`);
默认导入的特点:
- 每个模块只能有一个默认导出
- 导入时可以使用任意名称
- 不需要使用花括号
2.3 命名导入(Named Import)
命名导入用于导入模块中通过export
关键字导出的特定成员。
// api-client.ts
export interface ApiResponse<T> {data: T;status: number;message: string;
}export class ApiClient {async get<T>(url: string): Promise<ApiResponse<T>> {const response = await fetch(url);return {data: await response.json(),status: response.status,message: response.statusText};}
}export const API_BASE_URL = 'https://api.example.com';
export const DEFAULT_TIMEOUT = 5000;// constants.ts
export const APP_NAME = 'My Application';
export const VERSION = '1.0.0';
export const AUTHOR = 'TypeScript Developer';// main.ts - 使用命名导入
import { ApiClient, API_BASE_URL } from './api-client';
import { APP_NAME, VERSION, AUTHOR } from './constants';
import { ApiResponse } from './api-client'; // 只导入类型console.log(`${APP_NAME} v${VERSION} by ${AUTHOR}`);
const client = new ApiClient();
命名导入的特点:
- 可以导入多个特定成员
- 名称必须与导出时一致(除非重命名)
- 必须使用花括号包围
2.4 通配符导入(Namespace Import)
通配符导入将整个模块作为命名空间对象导入,可以访问模块的所有导出成员。
// utilities.ts
export function formatDate(date: Date): string {return date.toISOString().split('T')[0];
}export function formatCurrency(amount: number): string {return `$${amount.toFixed(2)}`;
}export function capitalize(str: string): string {return str.charAt(0).toUpperCase() + str.slice(1);
}export const DEFAULT_DATE_FORMAT = 'YYYY-MM-DD';
export const CURRENCY_SYMBOL = '$';// app.ts - 使用通配符导入
import * as utils from './utilities';console.log(utils.formatDate(new Date())); // 2023-12-07
console.log(utils.formatCurrency(123.45)); // $123.45
console.log(utils.capitalize('hello')); // Hello
console.log(utils.CURRENCY_SYMBOL); // $
通配符导入的使用场景:
- 需要使用模块中的多个成员
- 想要保持模块成员的命名空间组织
- 导入工具库的所有功能
2.5 无导入的副作用导入
有时我们只需要模块的副作用(如执行初始化代码),而不需要导入任何值。
// polyfills.ts - 填充库,只为了执行副作用
if (!Array.prototype.includes) {Array.prototype.includes = function(searchElement: any): boolean {return this.indexOf(searchElement) !== -1;};
}if (!String.prototype.padStart) {String.prototype.padStart = function(length: number, padString: string = ' '): string {return padString.repeat(Math.max(0, length - this.length)) + this;};
}console.log('Polyfills loaded');// styles.css.ts - CSS导入,只为了应用样式
import './global-styles.css';
import './theme.css';// main.ts - 导入副作用模块
import './polyfills'; // 执行polyfills
import './styles.css'; // 应用样式
这种导入方式常用于:
- 应用全局样式
- 执行初始化代码
- 注册全局事件监听器
3 高级import用法
3.1 混合导入模式
当模块同时有默认导出和命名导出时,可以使用混合导入模式。
// data-service.ts
export interface DataItem {id: number;name: string;
}export function processData(items: DataItem[]): DataItem[] {return items.map(item => ({ ...item, processed: true }));
}export function validateData(items: DataItem[]): boolean {return items.every(item => item.id > 0 && item.name.length > 0);
}// 默认导出
export default class DataService {private data: DataItem[] = [];add(item: DataItem): void {this.data.push(item);}getAll(): DataItem[] {return [...this.data];}
}// app.ts - 混合导入
import DataService, { DataItem, processData, validateData
} from './data-service';const service = new DataService();
const items: DataItem[] = [{ id: 1, name: 'Item 1' },{ id: 2, name: 'Item 2' }
];if (validateData(items)) {const processed = processData(items);processed.forEach(item => service.add(item));
}
混合导入的语法要点:
- 默认导出在前,命名导出在后
- 用逗号分隔默认导入和命名导入
- 默认导入不需要花括号,命名导入需要
3.2 导入重命名技巧
为了避免命名冲突或提高代码可读性,可以使用导入重命名。
// user-api.ts
export interface User {id: number;name: string;email: string;
}export async function getUser(id: number): Promise<User> {// 模拟API调用return { id, name: `User ${id}`, email: `user${id}@example.com` };
}// product-api.ts
export interface User { // 与user-api.ts中的User同名userId: number;username: string;emailAddress: string;
}export async function getProduct(id: number): Promise<any> {// 模拟API调用return { id, name: `Product ${id}` };
}// order-api.ts
export interface User { // 又一个User接口user_id: number;user_name: string;user_email: string;
}export async function getOrder(id: number): Promise<any> {// 模拟API调用return { id, orderId: `ORDER-${id}` };
}// app.ts - 使用重命名避免冲突
import { User as ApiUser } from './user-api';
import { User as ProductUser } from './product-api';
import { User as OrderUser } from './order-api';import { getUser as fetchUser } from './user-api';
import { getProduct as fetchProduct } from './product-api';// 现在可以清晰地区分不同的User类型
const apiUser: ApiUser = { id: 1, name: 'John', email: 'john@example.com' };
const productUser: ProductUser = { userId: 1, username: 'John', emailAddress: 'john@example.com' };
const orderUser: OrderUser = { user_id: 1, user_name: 'John', user_email: 'john@example.com' };// 使用重命名的函数
fetchUser(1).then(user => console.log(user));
fetchProduct(1).then(product => console.log(product));
重命名的其他用途:
// 使用更语义化的名称
import { createConnection as connectToDatabase } from './database';
import { format as formatDate } from 'date-fns';
import { get as lodashGet } from 'lodash';// 避免与本地变量冲突
const lodashGet = (obj: any, path: string) => obj[path];
import { get as _get } from 'lodash'; // 重命名避免冲突
3.3 动态导入(Dynamic Import)
动态导入允许在运行时按需加载模块,这对于代码分割和懒加载非常有用。
// heavy-module.ts - 假设这是一个大型模块
export class HeavyComputation {performComplexCalculation(data: number[]): number {// 模拟复杂计算return data.reduce((sum, num) => sum + Math.pow(num, 2), 0);