TypeScript中的 “冷门“ 语法

724 阅读5分钟

前言

现代的前端工程大多使用TypeScript,各种古老框架也不断对TypeScript进行支持,从而挽留用户,比如Jquery,一个后端用的比前端多的框架(戏言)。大多数场景下就单单使用interfaceenumtypeas Type基本能获得不错的类型提示,语法提示的收益,在遇到更加复杂的类型变化的时候一个any瞬间变回js,一个ts-ingore,没人找到我的错。其实对于这些热门语法,ts本身还有很多的冷门语法,代码的质量(b格)能从这些语法中得到很好的提高。

正文

1. never类型

我相信在很多人心中,never类型的使用场景一直是个谜,先看看文档是怎么说的。

never类型表示的是那些永不存在的值的类型。 例如, never类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型; 变量也可能是 never类型,当它们被永不为真的类型保护所约束时。

说的也还算清楚,那到底怎么用呢,职业话语讲怎么落地呢?其实本质上来说,任何类型的使用都是为了获得类型提示,never也不例外,看下尤大举的列子

type All = 'foo' | 'bar';
function handleValue(val: All) {
  switch (val) {
    case "foo":
      // 这里 val 被收窄为 Foo
      break;
    case "bar":
      // val 在这里是 Bar
      break;
    default: // val 在这里是 never
      const exhaustiveCheck: never = val;
      break;
  }
}

此时在default case中,val的类型就是never,这时候如果All类型新增一个枚举值,但忘记修改handleValue覆盖新增的case,就会得到类型提示,从而帮助开发者完善switch case的逻辑

image.png


2. as const 断言(v3.4)

也许见过这样的代码

const VALID_LAYOUT_VALUES = [
  'fill',
  'fixed',
  'intrinsic',
  'responsive',
  'raw',
  undefined,
] as const
type LayoutValue = typeof VALID_LAYOUT_VALUES[number]

这里面就用到了as const断言,看一下官方给的说明

Its syntax is a type assertion with const in place of the type name (e.g. 123 as const). When we construct new literal expressions with const assertions, we can signal to the language that

  • no literal types in that expression should be widened (e.g. no going from “hello” to string)
  • object literals get readonly properties
  • array literals become readonly tuples

简单来说就是可以给一个字面量使用 as const断言,他本身或者他(对象字面量)的属性将会获得下列三个buffer

  1. 字面量不会被ts推断为更广泛的类型
  2. 对象字面量的属性变为只读
  3. 数组字面量变为只读元组类型

举个stackoverflow上例子,有如下代码

const args = [8, 5];
const angle = Math.atan2(...args); 

这样会获得一个错误提示

A spread argument must either have a tuple type or be passed to a rest parameter.ts(2556)

因为args被推断为number[] ,可能会在某个地方被改动内部元素个数,ts无法判断数组的长度,也就可能不满足Math.atan2的参数类型,所以推断为number[] 类型是符合逻辑的,此时可以使用as const断言,使得args的类型变为 readonly [8,5] ,报错就会消失。

总的来说,as const断言使字面量类型收窄为定义时的类型,使得开发过程中类型更为安全,也更为智能,同时省去了不少重复的类型定义。

例如用在redux中,可以使用as const收窄action的类型,而不用重复定义返回值类型的interface | from const assertions are the killer new TypeScript feature

onst setCount = (n: number) => {
  return <const>{
    type: 'SET_COUNT',
    payload: n
  }
}
const resetCount = () => {
  return <const>{
    type: 'RESET_COUNT'
  }
}
type CountActions = ReturnType<typeof setCount> | ReturnType<typeof resetCount>;

3. 自定义的类型保护

有这样一个场景,当函数参数为联合类型的时候,需要在函数中多次使用联合类型中的其中一种类型的某些属性,此时就需要多次的使用类型断言来指定参数为特定类型,从而避免报错,获得代码提示

type Foo = {
  foo: string;
};
type Bar = {
  bar: number;
};
function foo(param: Foo | Bar) {
  (param as Foo).foo;
  (param as Foo).foo;
  (param as Foo).foo;
  (param as Foo).foo;
}

这无疑是非常麻烦的,而自定义类型保护则提供了一种机制,来让ts记住某个变量的类型,从而只需要进行一次类型判断就可以直接缩小类型范围

image.png

图中isFoo函数的定义就是进行类型保护,告知ts,将只要函数返回true,则表示参数稳定为Foo类型,对于内置的类型,ts已经支持通过instanceof和typeof两个老朋友实现这种效果。

image.png


4. ?. 与 ! 与 ??

?. 用的算是比较多的,不太能够算冷门属性,但因为对于还未实战的朋友在文档里很难发觉,这里也列了出来,学名是Optional Chaining,常用于接口多层对象返回值上,在没有可选链之前,对于接口的返回值是这样判断的

const getUserInfo = async () => {
  const ret = await API.getUserInfo();
  if (ret && ret.data && ret.data.status === 200) {
    // do something
  }
};

如果嵌套很深,if判断就会更长,此时就可以使用?.语法,在遇到null,或者undefined的时候,自动返回undefined,阶段后面属性的访问直接写成ret?.data?.status就好了。

另外还可以用在可能为undefined或者null变量当作函数调用上,列如foo?.()

--

至于 !(星号)那可是ts里的多面手,不仅扮演者经典取反的角色,还出演了很多不同的新角色

let x!: number[];
let y: string | undefined;
y!.charAt(0);

第一行的用法表示稍后会向x赋值,请ts将x看成了赋值数字数组的变量来检查
第三行的用法表示在y的所有可能的类型中剔除null和undefined,专业术语叫Non-null Assertion Operator

--
?? 用法如下

const a = b ?? c;

表示当b为null或者undefined时,值为c否则为b,这和&&的区别是,后者判断的依据是布尔真假,比如0为布尔假

持续更新...