TypeScript: 类型判断-合理的使用 is 和 type

25,670 阅读4分钟

TypeScript: Type predicates

TypeScript 类型判断--合理的使用 istype

  • 这篇文章主要写在使用函数的时候确保你的参数类型正确的规范的建议。

写在最前面

  • 最开始写 typescript 最困难的就是各种类型的判断,最近浏览 jsFeed 的时候看到一篇不错的文章,然后自己翻译了一下分享给大家。
  • 文章中的翻译都是义译,没有逐字逐段,很多不正确的地方望指出。

Type predicates in TypeScript help you narrowing down your types based on conditionals. They’re similar to type guards, but work on functions. They way the work is, if a function returns true, change the type of the paramter to something more useful.

typescript 的类型断言帮助你更好的规范你的代码类型。类型断言一般在函数中使用(work on functions),来确保你的函数类型返回正确。

is 的使用场景

step 1

  • Let’s start with a basic example. Let’s say you have a function that checks if a certain value is of type string:
  • 来看一个栗子
function isString(s) {
  return typeof s === 'string';
}

step 2

  • Use the isString function inside another function:
  • 在另外一个函数中使用上面的函数(isString())
function toUpperCase(x: unknown) {
  if(isString(x)) {
    x.toUpperCase(); // ⚡️ x is still of type unknown
  }
}

TypeScript throws an error. We can be sure that x is of type string at this point. But since the validation is wrapped in a function, the type of x does not change (as opposed to type guards). Enter type predicates.

ts 抛出了一个错误提示,我们能确信 x 是在类型判断为 string 以后再进行 toupperCase().但是由于这个检验函数(isString)被包裹在 toUpperCase()函数中,ts 在判断的时候还是抛出了错误提示。。

  • Let’s tell TypeScript explicitly that if isString evaluates to true, the type of the parameter is a string:

  • 使用 is ,这里让我们主动明确的告诉 ts ,在 isString() 这个函数的参数是一个 string。

// !!! 使用 is 来确认参数 s 是一个 string 类型
function isString(s): s is string {
  return typeof s === 'string';
}

  • TypeScript now knows that we are dealing with strings in our toUpperCase function.
  • 现在 ts 知道我们是使用 string 来处理 toUpperCase 函数了。
function toUpperCase(x: unknown) {
  if(isString(x)) {
    x.toUpperCase(); // ✅ all good, x is string
  }
}

Narrowing down sets

缩小参数类型

  • This not only helps you for unknown types, or multiple types, but also to narrow down sets within a type. Let’s have a program where you throw a dice. Every time you throw a Six, you win.
  • 虽然is 让 ts 分辨了 unknown 类型和 更多的其他类型,但是也让我们类型缩小了范围。为什么啦?
  • 来看一个栗子:让我们来做一个丢色子的游戏,当你丢到 6 的时候你就赢了。

step 1

function pipsAreValid(pips: number) {
  // we check for every discrete value, as number can 
  // be something between 1 and 2 as well.
  return pips === 1 || pips === 2 || pips === 3 ||
    pips === 4 || pips === 5 || pips === 6;
}

function evalThrow(count: number) {
  if (pipsAreValid(count)) {
    // my types are lying 😢
    switch (count) {
      case 1:
      case 2:
      case 3:
      case 4:
      case 5:
        console.log('Not today');
        break;
      case 6:
        console.log('Won!');
        break;
      case 7:
        // TypeScript does not complain here, even though
        // it's impossible for count to be 7
        console.log('This does not work!');
        break;
    }
  }
}

The program looks good at first, but has some issues from a type perspective: count is of type number. This is ok as an input parameter. Right away we validate that count is a number between 1 and 6. Once we validate this, count is not any number anymore. It’s narrowed down to a discrete set of six values.

上面的程序看起来没啥毛病,但是在类型预处理上还是有一些问题,count 是一个 number 类型,输入的参数是没有问题的。 在我们校验它的时候,count不再是一个 number 类型,而是变成了一个 1-6 number 的特殊类型。

  • So starting from the switch statement, my types are lying! To prevent any further complications, let’s narrow down the set of numbers to those six discrete values, using union types:

  • 从我们开始使用 switch 的时候,类型就开始发生变化了。ts 为了防止类型溢出,使用了联合类型把原来 number 类型变成 1-6的数字(缩小了范围)。

step 2

// 主动使用联合类型确保我们的输入是 1-6的数字
type Dice = 1 | 2 | 3 | 4 | 5 | 6;

function pipsAreValid(pips: number): pips is Dice {
  return pips === 1 || pips === 2 || pips === 3 ||
    pips === 4 || pips === 5 || pips === 6;
}

function evalThrow(count: number) {
  if (pipsAreValid(count)) {
    // count is now of type Dice 😎
    switch (count) {
      case 1:
      case 2:
      case 3:
      case 4:
      case 5:
        console.log('Not today');
        break;
      case 6:
        console.log('Won!');
        break;
      case 7:
        // TypeScript errors here. 7 is not in the union type of 
        // Dice
        console.log('This does not work!');
        break;
    }
  }
}

A lot type safer for us, and for our colleagues. Of course this “type casts” can be anything that makes sense to strengthen your applications. Even if you validate complex objects, you can narrow down your parameters to a specific type and make sure they get along with the rest of your code. Useful, especially if you rely on a lot of functions.

当你在使用更加复杂和功能繁多的函数的应用程序的时候,可以使用上面这种类型来缩小你的参数类型。即使你验证了复杂的对象,但是可以看到 ts 会主动的去 narrow down(缩小) 你的类型范围。来提示我们做好参数的类型预检查。

参考