为了账号安全,请及时绑定邮箱和手机立即绑定

掌握高级TypeScript技巧

简介

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. 结合这些工具如 PartialRequiredOmit,可以创建更符合需求的类型定义。

定义一个类型 `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. 处理共享类型包
建立一个共享类型包可以确保服务间的一致性,但也会给依赖管理带来挑战。

6. 类型缩减的挑战

在处理复杂的数据结构时,即使是经验丰富的开发人员也可能会被类型推断误导。

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


上帝真是奇迹般地工作着😭😭😭

图片描述
这是一张图片,点击可以查看。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消