saber酱的抱枕

生于忧患,死于安乐

09/1
14:00
学习

TypeScript 使用记录

最近我开始学习使用 TypeScript。先看了看教程,好像很简单的样子,就那些类型嘛,定义好类型就完事了。但是实际使用中却遇到了不少问题,在这里记录一些经验。

1. keyof 的使用

当要使用中括号获取对象的属性时,我写了以下代码:

const a = {
  item1: 'xxx',
  item2: 'xxx'
}

function getValue(arg: string) {
  return a[arg]
}

getValue('item1')

getValue 的参数是字符串,类型就写成 string,很合理嘛。但是报错了:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ item1: string; item2: string; }'.

类型'string'不能用作 a 的索引。我想不通,去问了别人,才明白 typescript 的意思是,arg 的类型应该是由对象的属性名组成的联合类型,如 arg: "item1" | "item2"。把参数的类型改成 arg: keyof typeof a就可以了。

keyof 操作符用于类型查询,返回类型的所有 key,组成联合类型。

2. ! 非空断言

我看的教程里有 as 断言,却没有讲非空断言 !,我又是问了别人才知道。

如下 DOM 操作:

const a = document.querySelector('#a')
a.parentNode.removeChild(a)

因为 querySelector 的返回值类型是 Element | null,所以第二行就报错了:“对象可能为 "null"”

并且因为类型的原因,编辑器也不会提示 a 的属性和方法。这时候就要在值的后面加上感叹号,断言这是一个非空值,类型里就没有 null 了,只剩下 Element 类型。这样就正常了。

修改后的代码如下:

const a = document.querySelector('#a')!
a.parentNode!.removeChild(a)

3. NodeListOf

有时候我们获取 DOM 元素时, ts 推断不出来具体的类型,只能给出笼统的 Element 类型,所以我们需要指定具体的类型。

同时我们可以配合 NodeListOf 指定 NodeList 集合,比如 document.querySelectorAll 的结果就是 NodeList 列表。

const list: NodeListOf<HTMLDivElement> = document.querySelectorAll('.xxx')

4. 索引签名

如下数组里包含了几个对象,他们的 key 和 value 都不一样,而且这个数组里有多少个对象也不确定,怎么定义它们呢?

const c = [{a:1}, {b: 2}, {c: "3"}];
// 定义类型
interface Arrayc {
[key: string]: number | string;
}

另一个例子:

interface Test {
  imgNumberPerWork: number
  displayCover: boolean
  [key: string]: number | boolean
}

const test: Test = {
  imgNumberPerWork: 0,
  displayCover: true
}

function setValue(key: keyof Test, val: any) {
  test[key] = val
}

setValue('imgNumberPerWork', 1)

变量 test 里有不同类型的值,当用 test[key] 赋值的时候,就需要 [key: string]: number | boolean 这个字符串索引签名。

索引签名的前半部分 [key: string] 接收指定类型的索引名(这里是 string),后半段表示这样的索引会返回什么类型的值(这里是 number | boolean)

如果给索引签名前面加上 readonly,那么它所表示的索引就是只读的,不能赋值了。

5. 指定 this 的类型

如果我们的函数中使用了 this,通常 typescript 都会报错:

"this" 隐式具有类型 "any",因为它没有类型注释。

一个很好的办法是,把 this 作为函数的第一个参数,指定类型。如下:

function xzTip(this:HTMLElement, arg: XzTipArg) {
  const tipText = this.dataset.tip
}

当编译 ts 文件的时候,typescript 会自动的去掉这个 this 参数,不影响 js 的运行。

6. 循环 Set 结构

如下错误代码:

let set = new Set()
set.add(1)
for (const val of set.values()) {
  // ...
}

set.values() 报错:
类型“IterableIteratorShim”必须具有返回迭代器的 "[Symbol.iterator]()" 方法。

因为 for of 循环的对象需要具有 iteratorSet 结构自带 iterator,直接循环 set,不需要 .values()

set.values() 其实就是 setiterator,如果循环它,就相当于去循环对象的遍历器,而不是循环对象本身,所以报错。但这样的代码在 JavaScript 里是可以正常运行的,可怕。

7. 设置 Promise 的类型

const p = new Promise<Blob>((resolve,reject)=>{
  resolve()
})

如果 Promise 后面没有加 ,那么默认的返回值就是 Promise。像上面那样就可以指定返回值为 Promise 了。

虽然看起来很简单,但我一开始不知道是这样写的,瞎JB试了半天,浪费了不少时间。

TypeScript 使用记录