
TypeScript的版本层出不穷,所以很容易错过一些新增的便利功能。以下是我在去年发现的一些有用的新功能。
未知类型
当处理来自第三方库或网络API的数据时,这些数据没有TypeScript的类型,我们通常会使用any 类型。然而,这意味着在数据上不会发生类型检查:
const person: any = await getPerson(id);
const postcode = person.address.postcode; // TypeScript doesn't yell at us but what if there is no address property ... 💥!
在这种情况下,unknown 类型是any 的一个强类型的替代品:
const person: unknown = await getPerson(id);
const postcode = person.address.postcode; // 💥 - Type error - Object is of type 'unknown'
unknown 类型迫使我们在与它交互时进行显式类型检查:
const person: unknown = await getPerson(id);
if (isPersonWithAddress(person)) { const postcode = person.address.postcode;
}
function isPersonWithAddress(person: any): person is Person { return "address" in person && "postcode" in person.address;}
只读数组
我们可能认为下面的情况会引发类型错误:
type Person = {
readonly name: string; readonly scores: number[];};
const bob: Person = {
name: "Bob",
scores: [50, 45]
};
bob.scores.push(60); // does this raise a type error?
不过,TypeScript对这一点非常满意。在数组属性名称前的readonly 关键字只能确保该属性不能被设置为不同的数组--数组本身并不是不可变的。
然而,我们现在可以在数组本身之前再加上一个readonly 关键字,使其成为不可变的:
type Person = {
readonly name: string;
readonly scores: readonly number[];};
const bob: Person = {
name: "Bob",
scores: [50, 45]
};
bob.scores.push(60); // 💥 - Type error - Property 'push' does not exist on type 'readonly number[]'
还有一个ReadonlyArray 的通用类型,也可以做同样的事情:
type Person = {
readonly name: string;
readonly scores: ReadonlyArray<number>; // same as readonly number[]};
const assertions
const 断言可以帮助我们创建不可变的结构:
function createGetPersonAction() {
return { type: "GetPerson" } as const;}
const getPersonAction = createGetPersonAction(); // `getPersonAction` is of type `{ readonly type: "GetPerson"; }`
下面是另一个例子,这次使用了const 断言的替代角括号语法:
type Person = {
id: number;
name: string;
scores: number[];
};
const people: Person[] = [
{ id: 1, name: "Bob", scores: [50, 45] },
{ id: 2, name: "Jane", scores: [70, 60] },
{ id: 3, name: "Paul", scores: [40, 75] }
];
function getPersonScores(id: number) {
const person = people.filter(person => person.id === id)[0];
return <const>[...person.scores];}
const scores = getPersonScores(1); // `scores` is of type `readonly number[]`
scores.push(50); // 💥 - Type error - Property 'push' does not exist on type 'readonly number[]'
const 断言还可以防止字面类型被扩大。下面的例子引发了一个类型错误,因为 被扩大为一个字符串。Status.Loading
function logStatus(status: "LOADING" | "LOADED") {
console.log(status);
}
const Status = {
Loading: "LOADING",
Loaded: "LOADED"
};
logStatus(Status.Loading); // 💥 - Type error - Argument of type 'string' is not assignable to parameter of type '"LOADING" | "LOADED"'
使用const 断言将导致Status.Loading 保持其类型为"LOADING" :
const Status = {
Loading: "LOADING",
Loaded: "LOADED"
} as const;
logStatus(Status.Loading); // Type is "LOADING"
Marius Schulz有一篇关于const 断言的深度文章。
可选链
可选链允许我们以一种优雅的方式处理属性可能是null 或undefined 的对象图。
考虑一下下面的例子:
type Person = {
id: number;
name: string;
address?: Address;
};
type Address = {
line1: string;
line2: string;
line3: string;
zipcode: string;
};
function getPersonPostcode(id: number) {
const person: Person = getPerson(id);
return person.address.zipcode; // 💥 - Type error - Object is possibly 'undefined'
}
该代码引发了一个类型错误,因为address 属性是可选的,因此可以是undefined 。可选的链式运算符(?)允许我们导航到zipcode 属性而不产生任何类型错误或运行时错误:
function getPersonPostcode(id: number) {
const person: Person = getPerson(id);
return person.address?.zipcode; // returns `undefined` if `address` is `undefined`}
如果address 属性在运行时是undefined ,那么undefined 将被函数返回。
空值凝聚
Nullish coalescing允许我们用一个不同的值来代替一个是null 或undefined 的值。
考虑一下下面的例子:
type Person = {
id: number;
name: string;
score?: number;
};
function getPersonScore(id: number): number {
const person: Person = getPerson(id);
return person.score; // 💥 - Type error - Type 'number | undefined' is not assignable to type 'number'
}
这段代码引发了一个类型错误,因为score 属性是可选的,因此,可以是undefined 。当左侧操作数为null 或undefined 时,nullish 凝聚操作数 (??) 允许使用一个不同的值(在右侧操作数中指定):
function getPersonScore(id: number): number {
const person: Person = getPerson(id);
return person.score ?? 0;}
nullish凝聚运算符比使用person.score || 0 更加稳健,因为对于任何错误的值都会返回0 ,而不仅仅是null 或undefined 。
Omit实用类型
Omit 工具类型允许从一个现有的类型中创建一个新的类型,并删除一些属性。
考虑一下下面的例子:
type Person = {
id: number;
name: string;
mobile: string;
email: string;
};
如果我们想创建一个没有任何联系细节的Person 类型,我们可以使用Omit 实用类型,如下所示:
type PersonWithoutContactDetails = Omit<Person, "mobile" | "email">; // { id: number; name: string; }
这样做的好处是,如果Person 发生变化,PersonWithoutContactDetails 类型就会发生变化,而我们不必去碰PersonWithoutContactDetails :
type Person = {
id: number;
name: string;
mobile: string;
email: string;
age: number;
};
// PersonWithoutContactDetails automatically becomes { id: number; name: string; age: number;}