面试题答案
一键面试1. 思路
- 强类型定义:在每个模块中,对输入和输出的接口进行严格的类型定义。使用TypeScript的接口(
interface
)或类型别名(type
)来明确模块间传递的数据结构和函数签名。例如,在一个用户管理模块,定义用户信息接口:
interface User {
id: number;
name: string;
email: string;
}
- 依赖倒置原则:高层模块不应该依赖低层模块,两者都应该依赖抽象。在TypeScript中,抽象可以通过接口来实现。比如,一个订单处理模块依赖于用户模块获取用户信息,订单模块不直接依赖用户模块的具体实现,而是依赖用户模块暴露的用户信息接口。
- 类型推断与约束:利用TypeScript的类型推断机制,让编译器自动推断变量和函数返回值的类型。同时,通过类型约束确保函数参数和返回值符合预期类型。例如:
function getUserById(id: number): User {
// 假设这里从数据库获取用户
const user: User = { id, name: 'John Doe', email: 'johndoe@example.com' };
return user;
}
- 使用泛型:当模块的逻辑具有通用性,不依赖于特定类型时,使用泛型来提高代码的复用性,同时保持类型安全。例如,一个通用的缓存模块:
class Cache<T> {
private data: Map<string, T> = new Map();
set(key: string, value: T) {
this.data.set(key, value);
}
get(key: string): T | undefined {
return this.data.get(key);
}
}
2. 实践方法
- 单元测试:为每个模块编写单元测试,使用测试框架(如Jest)来验证模块接口的正确性。测试用例应该覆盖不同的输入场景,确保模块输出符合类型定义。例如,对于上述
getUserById
函数:
import { getUserById } from './userModule';
test('getUserById should return a valid User', () => {
const user = getUserById(1);
expect(user.id).toBe(1);
expect(typeof user.name).toBe('string');
expect(typeof user.email).toBe('string');
});
- 持续集成(CI):将单元测试集成到CI流程中,每次代码提交时自动运行测试。这可以及时发现由于代码修改导致的接口不兼容问题。
- 文档化类型:对模块的接口进行文档化,使用工具(如TSDoc)在代码中添加注释,说明接口的用途、输入输出要求等。例如:
/**
* 根据用户ID获取用户信息
* @param id 用户ID
* @returns 用户信息对象
*/
function getUserById(id: number): User {
// 实现代码
}
- 代码审查:在团队开发中,进行代码审查。审查人员检查新代码是否符合已定义的接口规范,是否对其他模块的接口产生影响。
3. 项目案例
假设我们有一个电商项目,包含商品模块、订单模块和用户模块。
- 商品模块:定义商品接口
Product
,并提供获取商品列表、获取单个商品等函数。
interface Product {
id: number;
name: string;
price: number;
}
function getProductList(): Product[] {
// 从数据库获取商品列表
return [];
}
function getProductById(id: number): Product | undefined {
// 从数据库获取单个商品
return undefined;
}
- 订单模块:依赖商品模块获取商品信息来创建订单。订单模块通过
Product
接口来确保获取的商品信息符合预期。
import { Product } from './productModule';
interface OrderItem {
product: Product;
quantity: number;
}
interface Order {
id: number;
items: OrderItem[];
totalPrice: number;
}
function createOrder(productIds: number[], quantities: number[]): Order {
const items: OrderItem[] = [];
let totalPrice = 0;
productIds.forEach((id, index) => {
const product = getProductById(id);
if (product) {
const quantity = quantities[index];
const item: OrderItem = { product, quantity };
items.push(item);
totalPrice += product.price * quantity;
}
});
const order: Order = { id: Math.random(), items, totalPrice };
return order;
}
- 重构时:如果商品模块需要对
Product
接口进行修改,比如添加一个description
字段。由于订单模块是通过Product
接口来获取商品信息,只要在修改Product
接口后,更新相关函数(如getProductById
、getProductList
)的返回类型,并且确保订单模块中使用Product
接口的地方能兼容新的接口定义(通过单元测试验证),就可以最小化对订单模块的影响。同时,在CI流程中运行测试,确保整个项目的接口兼容性。