面试题答案
一键面试基于类型保护和类型守卫进行架构优化
- 提升可维护性
- 使用类型断言和类型守卫:在不同模块数据交互时,通过类型守卫(如
typeof
、instanceof
等)进行运行时类型检查。例如:
- 使用类型断言和类型守卫:在不同模块数据交互时,通过类型守卫(如
function handleData(data: unknown) {
if (typeof data ==='string') {
// 此时 data 被类型保护为 string 类型
console.log(data.length);
}
}
这样在后续维护时,开发者能清晰知道每个分支处理的数据类型,减少因类型不明确导致的错误。 - 创建类型保护函数:对于复杂的类型检查逻辑,可封装成类型保护函数。比如:
interface User {
name: string;
age: number;
}
function isUser(obj: unknown): obj is User {
return typeof (obj as User).name ==='string' && typeof (obj as User).age === 'number';
}
function processUser(data: unknown) {
if (isUser(data)) {
// data 在此分支被类型保护为 User 类型
console.log(data.name);
}
}
这种方式提高了代码的复用性,使得代码更易于理解和维护。 2. 增强可扩展性 - 利用联合类型与类型保护:当模块需要处理多种不同类型的数据时,使用联合类型,并结合类型保护来处理不同情况。例如:
type Shape = { type: 'circle'; radius: number } | { type:'rectangle'; width: number; height: number };
function draw(shape: Shape) {
if (shape.type === 'circle') {
// shape 被类型保护为圆形相关类型
console.log(`Drawing a circle with radius ${shape.radius}`);
} else {
// shape 被类型保护为矩形相关类型
console.log(`Drawing a rectangle with width ${shape.width} and height ${shape.height}`);
}
}
未来如果需要新增一种形状类型,只需修改联合类型并在 draw
函数中添加对应的类型保护分支即可,对现有代码影响较小。
- 基于泛型与类型保护:在一些通用组件或函数中使用泛型,并通过类型保护来处理不同类型参数。例如:
function identity<T>(arg: T): T {
return arg;
}
function processIdentity<T>(arg: T) {
const result = identity(arg);
if (Array.isArray(result)) {
// result 被类型保护为数组类型
console.log(`Array length: ${result.length}`);
}
return result;
}
这样在项目扩展新功能时,泛型结合类型保护能灵活适应不同类型需求。 3. 提高运行效率 - 避免不必要的类型检查:合理安排类型保护逻辑,避免在循环等性能敏感区域进行复杂且不必要的类型检查。例如:
// 错误示例,在循环内进行复杂类型检查
function badPerformance() {
const data: unknown[] = [1, 'two', {name: 'obj'}];
for (let i = 0; i < data.length; i++) {
if (typeof (data[i] as {name: string}).name ==='string') {
// 此处的类型检查较为复杂且每次循环都执行
}
}
}
// 正确示例,提前筛选出需要处理的数据
function goodPerformance() {
const data: unknown[] = [1, 'two', {name: 'obj'}];
const filteredData = data.filter((item) => typeof (item as {name: string}).name ==='string');
for (let i = 0; i < filteredData.length; i++) {
// 仅处理已筛选的数据,减少不必要检查
}
}
- **利用类型推断**:充分利用 TypeScript 的类型推断功能,减少显式类型声明。例如:
let num = 10; // num 被推断为 number 类型,无需显式声明 let num: number = 10;
这样可以减少代码冗余,同时提高编译和运行效率。
避免潜在类型相关问题
- 性能瓶颈
- 缓存类型检查结果:对于多次使用的类型检查结果,可以进行缓存。例如:
function complexTypeCheck(data: unknown): boolean {
// 复杂的类型检查逻辑
return true;
}
function processData(data: unknown) {
const isCorrectType = complexTypeCheck(data);
if (isCorrectType) {
// 使用 data
}
// 后续如果还需要检查类型,直接使用 isCorrectType 变量
}
- **减少动态类型使用**:虽然动态类型(如 `unknown`、`any`)在某些场景有其便利性,但过多使用会导致类型检查无法在编译时完成,增加运行时性能开销。尽量在早期将数据转换为具体类型。
2. 错误避免 - 全面的类型覆盖:在编写类型保护逻辑时,要确保覆盖所有可能的类型情况。例如,在处理联合类型时,要处理联合类型中的每一种类型分支,避免遗漏。
type Fruit = 'apple' | 'banana' | 'cherry';
function printFruit(fruit: Fruit) {
if (fruit === 'apple') {
console.log('It's an apple');
} else if (fruit === 'banana') {
console.log('It's a banana');
} else {
// 处理 cherry 情况,避免遗漏
console.log('It's a cherry');
}
}
- **使用严格的类型设置**:在项目配置文件(如 `tsconfig.json`)中启用严格模式,如 `"strict": true`,这会强制进行更严格的类型检查,提前发现潜在的类型错误。
- **单元测试**:编写单元测试来验证类型保护和类型守卫的逻辑正确性,确保在各种输入情况下都能正确处理类型。例如,使用 Jest 等测试框架对类型保护函数进行测试:
import { isUser } from './userUtils';
describe('isUser', () => {
it('should return true for valid User object', () => {
const user: User = {name: 'John', age: 30};
expect(isUser(user)).toBe(true);
});
it('should return false for invalid object', () => {
const invalidObj = {name: 'John'};
expect(isUser(invalidObj)).toBe(false);
});
});