要记住的事:
- 优先选择类型声明(:Type)而不是类型断言(as Type)
- 知道如何注释一个箭头函数的返回类型
- 当你知道一些类型而TypeScript不知道时,使用类型断言和非空断言
TypeScript似乎有两种方法来给变量赋值并赋予一个类型:
interface Person { name: string; }
const alice: Person = { name: 'Alice' }; // 类型是Person
const bob = { name: 'Bob' } as Person; // 类型是Person
虽然这两种方法的目的相似,实际上完全不同。第一种是给变量添加了一个类型声明,并确保值符合类型。第二种执行了一个类型断言,这告诉TypeScript,尽管它推断出了类型,但你更了解它,并希望类型是Person.
一般来说,你应该更喜欢类型声明而不是类型断言。因为:
const alice: Person = {}; // 类型“{}”中缺少属性“name”,但在类型Person中为必选
const alice: Person = {
name: 'Alice',
occupation: 'Developer' // ~~提示错误,occupation不在类型“Person”中
};
const bob = {} as Person; // 没错误
const bob = {
name: 'Bob',
occupation: 'Developer'
} as Person; // 没错误
类型声明验证了这个值符合接口要求,如果不符合,TypeScript就会标记一个错误。类型断言通过推断告诉类型检查器,不管出于什么原因,你比它更了解这个错误。类型声明提供了透明的安全检查,你应该使用它,除非你有特定的理由使用类型断言。
该如何对箭头函数使用类型声明并不总是很清楚的。比如:
const people = ['alice', 'bob', 'jan'].map(name => ({name}));
// { name: string; }[] ...但我们需要Person
在这里使用类型断言比较容易:
const people = ['alice', 'bob', 'jan'].map(name => ({name} as Person));
// 类型是 Peron[],但这和更直接的类型断言一样
const people = ['alice', 'bob', 'jan'].map(name => ({} as Person));
那么如何在这种情况下使用类型声明?如下声明箭头函数的返回类型:
const people = ['alice', 'bob', 'jan'].map(
(name): Person => ({name})
); // 类型是Person[]
这样执行所有与前面一样的值检查。(name): Person推断出name的类型,并指定返回的类型应该是Person,这里的括号很重要!
但是,在一个较长的函数调用链的上下文中,可能有必要或希望尽早出现命名的类型,这将有助于在错误发生的地方标记错误。
那么什么时候应该使用类型断言呢?当你确实比TypeScript知道更多关于类型的信息时,类型断言是最有意义的。下面示例中,TypeScript无法访问你的页面DOM,它无法知道#myButton是一个按钮元素,也不知道事件中currentTarget应该是同一个按钮。由于你有TypeScript没有的信息,因此类型断言在这里是有意义的。
document.querySelector('#myButton').addEventListener('click', e => {
e.currentTarget // 类型是EventTarget
const button = e.currentTarget as HTMLButtonElement; // button类型是HTMLButtonElement
});
类型断言也有其局限性:它们不能让你在任意类型之间进行转换。一般,我们可以使用类型断言在A和B之间进行转换,只要其中一个是另一的子集。比如HTMLButtonElement是EventTarget的子集,e.currentTarget as HTMLButtonElement是可以的,Person是{}的子类型,{ } as Person是可以的。
但是我们不能在Person和HTMLElement之间进行转换,两者都不是对方的子类型。
interface Person { name: string; }
const body = document.body;
const el = body as Person // ~~提示错误,这两个类型没有充分地重叠。如果有意为之,请先将表达式转换为“unknown”。
每个类型都是unknown的子类型,所以涉及unknown的断言总是正确的,这让你可以在任意类型之间进行转换,但至少你是显式地在做一些令人生疑的事情。
const el = document.body as unknown as Person; // OK