TypeScript 核心特性详解:枚举、泛型、命名空间和模块
TypeScript 为 JavaScript 带来了强大的类型系统,让大型项目开发更加稳健。今天我们来深入理解枚举、泛型、命名空间和模块这几个核心概念。
枚举:给数值起个好名字
枚举是 TypeScript 中的特殊数据类型,让我们可以为数值设置有意义的名字。
数值枚举
enum Direction {
Up = 1, // 从1开始
Down, // 自动变成2
Left, // 自动变成3
Right // 自动变成4
}
// 使用枚举
let move: Direction = Direction.Up;
console.log(move); // 输出: 1如果不指定初始值,枚举会从0开始自动递增:
enum Status {
Pending, // 0
Approved, // 1
Rejected // 2
}字符串枚举
enum Response {
Yes = "YES",
No = "NO"
}
let answer: Response = Response.Yes;
console.log(answer); // 输出: "YES"字符串枚举不会自动递增,每个成员都必须初始化。
常量枚举
使用 const enum 可以创建常量枚举,TypeScript 会在编译时进行优化:
const enum Size {
Small,
Medium,
Large
}
// 编译后直接使用数值,不会生成额外的JavaScript代码
let mySize = Size.Medium; // 编译为: let mySize = 1;异构枚举(不推荐)
TypeScript 支持数字和字符串混用的枚举,但通常不建议这样做:
enum BooleanLikeHeterogeneousEnum {
No = 0,
Yes = "YES"
}这种混合使用会增加代码的复杂性,建议保持枚举成员类型一致。
枚举作为类型
枚举成员可以作为类型使用:
enum ShapeKind {
Circle,
Square,
}
interface Circle {
kind: ShapeKind.Circle; // 必须是ShapeKind.Circle
radius: number;
}
interface Square {
kind: ShapeKind.Square; // 必须是ShapeKind.Square
sideLength: number;
}
function createCircle(radius: number): Circle {
return {
kind: ShapeKind.Circle, // 正确
radius: radius
};
}泛型:编写可复用的类型安全代码
泛型让我们可以创建可重用的组件,同时保持类型安全。
函数泛型
// 基础泛型函数
function identity<T>(arg: T): T {
return arg;
}
// 使用泛型函数
let numberResult = identity<number>(42); // 类型为 number
let stringResult = identity<string>("hello"); // 类型为 string
let inferredResult = identity(true); // TypeScript 自动推断类型为 boolean接口泛型
// 泛型接口
interface KeyValuePair<K, V> {
key: K;
value: V;
}
// 使用泛型接口
let numberPair: KeyValuePair<number, string> = {
key: 1,
value: "one"
};
let stringPair: KeyValuePair<string, boolean> = {
key: "isActive",
value: true
};类泛型
class Container<T> {
private value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
setValue(newValue: T): void {
this.value = newValue;
}
}
// 使用泛型类
let numberContainer = new Container<number>(100);
console.log(numberContainer.getValue()); // 输出: 100
let stringContainer = new Container<string>("TypeScript");
console.log(stringContainer.getValue()); // 输出: "TypeScript"泛型约束
有时候我们需要限制泛型的类型范围:
// 要求泛型必须有 length 属性
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(arg: T): void {
console.log(arg.length);
}
logLength("hello"); // 正确,字符串有length属性
logLength([1, 2, 3]); // 正确,数组有length属性
logLength(42); // 错误,数字没有length属性实际应用示例
// api响应包装器
interface ApiResponse<T> {
success: boolean;
data: T;
message?: string;
}
// 用户数据接口
interface User {
id: number;
name: string;
email: string;
}
// 使用泛型接口
function fetchUser(userId: number): ApiResponse<User> {
// 模拟API调用
return {
success: true,
data: {
id: userId,
name: "张三",
email: "zhangsan@example.com"
}
};
}
// 分页数据
interface PaginatedList<T> {
items: T[];
total: number;
page: number;
pageSize: number;
}
type UserList = PaginatedList<User>;命名空间:代码组织的传统方式
命名空间帮助我们将相关代码组织在一起,避免全局命名冲突。
创建命名空间
namespace MathUtils {
export const PI = 3.14159;
export function calculateArea(radius: number): number {
return PI * radius * radius;
}
export function calculateCircumference(radius: number): number {
return 2 * PI * radius;
}
}
// 使用命名空间
console.log(MathUtils.PI); // 输出: 3.14159
let area = MathUtils.calculateArea(5); // 输出: 78.53975注意:需要使用 export 关键字才能在命名空间外部访问成员。
嵌套命名空间
namespace Company {
export namespace HR {
export function hireEmployee(name: string): void {
console.log(`雇佣员工: ${name}`);
}
}
export namespace Finance {
export function calculateSalary(hours: number): number {
return hours * 50;
}
}
}
// 使用嵌套命名空间
Company.HR.hireEmployee("李四");
let salary = Company.Finance.calculateSalary(40);命名空间的使用场景
第三方库开发
namespace MyChartLibrary {
export function createLineChart(data: number[]): void {
// 创建折线图逻辑
}
export function createBarChart(data: number[]): void {
// 创建柱状图逻辑
}
}
// 使用库
MyChartLibrary.createLineChart([1, 2, 3, 4]);与旧代码兼容
// 旧有的全局变量
declare var LegacyApp: any;
namespace ModernApp {
export function initialize(): void {
// 与旧代码交互
if (LegacyApp) {
LegacyApp.setup();
}
}
}模块:现代代码组织方式
模块是当前推荐的代码组织方式,每个文件都是一个模块。
创建和使用模块
math.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 {
static square(x: number): number {
return x * x;
}
}app.ts
// 命名导入
import { PI, add, multiply } from './math';
// 默认导入
import Calculator from './math';
// 使用导入的功能
console.log(PI); // 输出: 3.14159
let sum = add(5, 3); // 输出: 8
let squared = Calculator.square(4); // 输出: 16模块重命名
// 重命名导入
import { PI as圆周率, add as 相加 } from './math';
console.log(圆周率); // 输出: 3.14159
let 结果 = 相加(2, 3); // 输出: 5批量导入
// 导入所有导出
import * as MathFunctions from './math';
console.log(MathFunctions.PI); // 输出: 3.14159
let product = MathFunctions.multiply(4, 5); // 输出: 20命名空间 vs 模块:如何选择
主要区别
| 特性 | 命名空间 | 模块 |
|---|---|---|
| 作用域 | 全局作用域 | 文件作用域 |
| 文件组织 | 通常在同一文件中 | 跨文件组织 |
| 依赖管理 | 需要手动管理 | 自动处理依赖 |
| 现代性 | 传统方式 | 现代标准 |
使用建议
使用模块的情况:
新项目开发
需要清晰的依赖关系
与现代构建工具配合
代码需要树摇优化
使用命名空间的情况:
维护旧项目
开发全局可用的库
需要将多个文件合并输出
与现有的命名空间代码交互
实际项目结构示例
src/
├── models/ // 数据模型
│ ├── user.ts
│ └── product.ts
├── services/ // 业务逻辑
│ ├── api.ts
│ └── auth.ts
├── utils/ // 工具函数
│ └── helpers.ts
└── app.ts // 入口文件user.ts(模块示例)
export interface User {
id: number;
name: string;
email: string;
}
export function createUser(name: string, email: string): User {
return {
id: Date.now(), // 简单ID生成
name,
email
};
}api.ts(模块示例)
import { User } from '../models/user';
export class ApiService {
static async fetchUser(userId: number): Promise<User> {
const response = await fetch(`/api/users/${userId}`);
return response.json();
}
static async saveUser(user: User): Promise<void> {
await fetch('/api/users', {
method: 'POST',
body: JSON.stringify(user)
});
}
}总结
TypeScript 提供了多种代码组织方式:
枚举:让数值更有意义,提高代码可读性
泛型:创建可重用且类型安全的组件
命名空间:传统的代码组织方式,适合特定场景
模块:现代代码组织标准,推荐在新项目中使用
在实际开发中,建议优先使用模块来组织代码,配合枚举和泛型来增强类型安全。只有在需要与旧代码兼容或开发特定类型的库时,才考虑使用命名空间。
掌握这些特性可以帮助你编写更健壮、更易维护的 TypeScript 代码,为大型项目开发打下坚实基础。
本文内容仅供个人学习/研究/参考使用,不构成任何决策建议或专业指导。分享/转载时请标明原文来源,同时请勿将内容用于商业售卖、虚假宣传等非学习用途哦~感谢您的理解与支持!