前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >TypeScript 5.5 ,即将支持自动推断类型守卫!

TypeScript 5.5 ,即将支持自动推断类型守卫!

作者头像
ConardLi
发布2024-04-03 18:36:15
2370
发布2024-04-03 18:36:15
举报
文章被收录于专栏:code秘密花园

就在上周,TypeScript 合并了一个 PR(https://github.com/microsoft/TypeScript/pull/57465):

这个 PR 受到了大家的广泛欢迎和支持:

它给 TypeScript 带来了强大的类型谓词(type predicates)自动推断能力,预计会在 TypeScript 5.5 版本中推出。

我们现在看看最直接的效果,比如下面这段代码:

代码语言:javascript
复制
function isString(x: string | number) {
  return typeof x === 'string';
}

在当前的版本中,它的类型推断是这样的:

然后在这个 PR 被发布后,类型推断将会变成这样:

这个变化有啥用呢?

我们先来回顾一下我之前的这篇文章:什么是鸭子🦆类型?

鸭子类型

鸭子类型是很多面向对象语言中的常见做法。它的名字来源于所谓的“鸭子测试”:

当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。我们不用关心鸭子的定义是什么,只要符合我们通常意义上的认知,那么他就是这个物体。

TypeScript 中,只要对象符合定义的类型约束,那么我们就可以视为他是。

在一些动态语言中,鸭子类型的常见用法就是假设给定值是符合我们预期的,你可以先尝试执行一个操作,然后我们再去处理不符合预期的情况下的异常。比如在下面这段 Python 代码中:

代码语言:javascript
复制
from typing import Any

def is_duck(value: Any) -> bool:
    try:
        value.quack()
        return True
    except (Attribute, ValueError):
    return False

这段代码写的很蠢,不过表达的意思挺明确的,你通过调用传入参数的 .quack() 方法检查它是否可以嘎嘎叫,如果它嘎嘎叫了,就返回 true ,如果它没有这个方法,异常就会被捕获,则返回 false。

相比之下,在 TypeScript 中,try-catch 则存在很多限制 — 你既不能根据抛出异常的原型定义不同的 catch 块,也不能确定抛出的到底是不是一个异常实例。如果用 TypeScript 的话写法可能就不一样了,参数 value 可能是只鸭子,但 IDE 和 JavaScript 解析器都不知道鸭子是啥。在 TypeScript 中,我们可以把鸭子定义成一个类型:

代码语言:javascript
复制
interface Duck {
  quack(): string;
}

interface Cat {
  miao(): string;
}


function isDuck(value: Cat | Duck) {
  return !!(
    value &&
    typeof value === 'object' &&
    typeof (value as Duck).quack === 'function'
  );
}

我们在实际使用时,会使用 isDuck 方法来判断目标是否是 Duck 类型,然后会去执行目标上的方法,这时就会报错:

代码语言:javascript
复制
function main(value: Duck | Cat) {
  if (isDuck(value)) { // roperty 'quack' does not exist on type 'Duck | Cat'.
    value.quack();
  }
}

类型守护

这种情况,我们一般用类型守卫来解决这个问题:

代码语言:javascript
复制
function isDuck(value: unknown): value is Duck {
  return !!(
    value &&
    typeof value === 'object' &&
    typeof (value as Duck).quack === 'function'
  );
}

// 上面的 value is Duck 告诉了 TypeScript,value 就是 Duck 类型
function main(value: Duck | Cat) {
  if (isDuck(value)) { // 不报错了
    value.quack();
  }
}

isDuck 的返回值类型中使用了 is 关键字,这在 TypeScript 中被叫做类型谓词(type predicates),类型谓词是一个返回布尔值的函数,可以用来做类型保护;

类型保护是可执行运行时检查的一种表达式,用于确保该类型在一定的范围内。换句话说,类型保护可以保证一个字符串是一个字符串,尽管它的值也可以是一个数字。

实际上它就是告诉 TypeScript 编译器给定的值是就是我们给定的那个类型。

简单的说,就是告诉编译器这个可能是鸭子的东西就是一只鸭子。

类型保护,也是类型收窄的一种方式。

在生产环境中,一般我们会实现一个通用的类型守卫工具函数:

代码语言:javascript
复制
export const isOfType = <T>(
  varToBeChecked: unknown,
  propertyToCheckFor: keyof T
): varToBeChecked is T =>
  Boolean(varToBeChecked) && (varToBeChecked as T)[propertyToCheckFor] !== undefined;

然后我们就可以这样用:

代码语言:javascript
复制
function main(value: Duck | Cat) {
  if (isOfType<Duck>(value, 'quack')) { // 通用的类型判断
    value.quack();
  }
}

自动推断

那么,这个特性所实现的功能就非常清晰了,还是相当强大的!

回到前面的例子,有下面的代码:

代码语言:javascript
复制
function isString(x: string | number) {
  return typeof x === 'string';
}

将自动推断为下面的类型:

也就是说,我们在调用 isString 函数时,不需要主动去实现类型守卫了:

代码语言:javascript
复制
if (isString(value)) {
  console.log(value); // string
}

我们再来看上面的例子:

代码语言:javascript
复制
function isDuck(value: unknown) {
  return !!(
    value &&
    typeof value === 'object' &&
    typeof Duck.quack === 'function'
  );
}

然后在以下场景中调用,完全不会出现问题了:

代码语言:javascript
复制
function main(value: Duck | Cat) {
  if (isDuck(value)) { 
    value.quack();
  }
}

它甚至可以实现更复杂一点的类型:

另外在很多其他复杂的场景,都可以很方便的自动推断类型保护,例如调用 filter

代码语言:javascript
复制
const nums = [17, "ConadLi", 17, "code秘密花园"].filter(x => typeof x === 'number');
//    ^? const nums: number[]

毫不夸张的说,我认为这是 TypeScript 最几个版本中我觉得最有用的一个特性,其实算是修复了 TypeScript 类型推断的一个长期存在的缺陷,可以让捕获函数中的类型收窄逻辑变得更加简单!

最后

大家觉得这个特性怎么样?欢迎在评论区留言:

参考:https://github.com/microsoft/TypeScript/pull/57465

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-03-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 code秘密花园 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 鸭子类型
  • 类型守护
  • 自动推断
  • 最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档