TypeScript 通常被视为现代 web 开发中的颠覆者,它弥合了动态语言和静态类型语言之间的鸿沟。然而,即使是经验丰富的开发人员在处理其高级功能时,也可能发现自己在十字路口。本文深入探讨 TypeScript 的细微之处,提供了对高级概念和解决常见挑战的创意方案的见解。
1. 高级类型处理技巧TypeScript 以其强大的类型系统而闻名,但在高级类型操作方面真正出类拔萃的是当你精通高级类型操作时。
a. 映射类型
映射类型让开发人员能够动态地转换现有类型并创建新的类型。虽然基本使用方法很简单,但当与条件类型结合使用时,可能会带来更复杂的挑战。
type ReadonlyPartial<T> = {
readonly [K in keyof T]?: T[K];
};
切换到全屏模式,退出全屏
挑战是: 在深层嵌套的对象上应用映射类型,同时保持类型完整性。
解决方案: 可以结合使用递归条件类型和实用类型。
类型 深度只读<T> = {
只读 [K in keyof T]: T[K] 是对象 ? 深度只读<T[K]> : T[K];
};
全屏,退出全屏
b. 映射类型中的键映射调整(TS 4.1+)
这功能让你可以在遍历类型时调整键。
定义一个类型 `RenameKeys<T>`,它接受一个泛型类型 `T` 并返回一个新对象,其中每个键都以 `new_` 开头:
type RenameKeys<T> = {
[K in keyof T as `new_${string & K}`]: T[K];
};
全屏,退出全屏
_创意交流:_ 我们如何确保按键映射与外部API兼容?
## 2. 复杂泛型
泛型让TypeScript更灵活,但过于复杂的限制可能让人难以应对。
**a. 通用条件类型**
在处理结构各不相同的API时,条件泛型就变得非常重要了。
type ApiResponse<T> = T extends { success: true } ? T['data'] : never;
进入全屏,退出全屏
**b. 常用的推断技巧**
通过函数参数推断类型可以使使用更简单,但需要仔细规划以避免结果模糊不清。
function transform<T extends { id: number }>(item: T): T['id'] {
return item.id;
}
这里是一个函数 `transform`,它接受一个泛型类型 `T`,要求 `T` 有一个 `id` 属性,该属性为数字类型。函数返回 `item` 的 `id` 属性值。
切换到全屏模式 退出全屏模式
_难题:_ 我们如何防止推导出的类型无意中变窄?
## 3\. 高级实用工具类型
TypeScript 内置了几个实用类型工具,但可以通过创造性地扩展和组合它们来获得独特的解决方案。
**a. 自定义实用工具类型**
开发人员经常会为特定场景定制实用工具类型。
定义可变类型 Mutable<T> 为 {
- 只读 [K in T 的键名]: T[K];
};
如果你想进入全屏模式,就点击这里;如果你想退出全屏模式,也点击这里
b. 结合这些工具如 Partial
、Required
和 Omit
,可以创建更符合需求的类型定义。
定义一个类型 `MutablePick`,它接收两个泛型 `T` 和 `K`,其中 `K` 必须是 `T` 的键类型。这个类型表示 `T` 的部分属性是可变的,这些属性由 `K` 指定。其他属性则保持不变,通过 `Omit<T, K>` 排除这些指定的 `K` 属性。
全屏 退出全屏
4. 高级装饰模式虽然 TypeScript 中的装饰器还在试验阶段,但它们在元编程方面却非常强大。
a. 属性修饰器
装饰器可以用来验证、转换格式或监控属性的使用情况。
function 验证(target: any, propertyKey: string) {
let value = target[propertyKey];
Object.defineProperty(target, propertyKey, {
get() {
return value;
},
set(newValue) {
if (typeof newValue !== 'string') {
throw new Error('无效的输入值');
}
value = newValue;
},
});
}
全屏模式;退出全屏
b. 用例:API 缓存
实现用于缓存 API 响应的装饰器功能可以减少重复代码。
function CacheResult() {
const cache = new Map(); // 声明一个新的Map对象用于缓存
return function (target: any, key: string, descriptor: PropertyDescriptor) { // 返回一个描述器修饰器函数
const original = descriptor.value; // 保存原始方法
descriptor.value = function (...args: any[]) { // 重写方法,以实现缓存逻辑
const key = JSON.stringify(args); // 使用JSON.stringify将参数转换为键
if (!cache.has(key)) { // 如果缓存中没有该键
cache.set(key, original.apply(this, args)); // 将计算结果存入缓存
}
return cache.get(key); // 返回缓存中的值
};
};
}
全屏显示 退出全屏
5. TypeScript 单仓在 monorepo 里管理 TypeScript 项目可能会因为共享类型依赖项和协调版本问题而变得越来越复杂。
a. 项目引用
TypeScript 的项目引用功能支持增量构建,同时在多仓库中提供更佳的类型检查。
{
"references": [{ "path": "./common" }, { "path": "./service" }]
}
全屏模式 退出全屏
b. 处理共享类型包
建立一个共享类型包可以确保服务间的一致性,但也会给依赖管理带来挑战。
在处理复杂的数据结构时,即使是经验丰富的开发人员也可能会被类型推断误导。
a. 全面检查
使用 never
类型的方式保证联合中的所有情况都被覆盖。
type Shape = { kind: 'circle'; radius: number } | { kind: 'square'; side: number };
function area(shape: Shape): number {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2;
case 'square':
return shape.side ** 2;
default:
const _exhaustive: never = shape;
throw new Error('未处理的形状类型');
}
}
切换到全屏/退出全屏
b. 复杂对象的守卫
自定义类型守卫在验证嵌套对象方面非常重要。
function 是Person(obj: any) {
return obj && typeof obj.name === 'string' && typeof obj.age === 'number';
}
进入全屏模式 退出全屏模式
结尾TypeScript 提供了一个丰富的类型系统,激发创意并保证精准。高级功能如映射类型、复杂泛型和装饰器让开发者能够应对复杂挑战,但要有效使用它们也需要深入的理解。通过探索和掌握这些高级概念,开发者可以充分发挥 TypeScript 的潜力,创建可扩展且易于维护的应用程序。
我的网址是:https://shafayet.zya.me
上帝真是奇迹般地工作着😭😭😭
这是一张图片,点击可以查看。