你需要知道的6个有用的TypeScript 3功能

87 阅读4分钟

TypeScript 3

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 断言的深度文章

可选链

可选链允许我们以一种优雅的方式处理属性可能是nullundefined 的对象图。

考虑一下下面的例子:

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允许我们用一个不同的值来代替一个是nullundefined 的值。

考虑一下下面的例子:

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 。当左侧操作数为nullundefined 时,nullish 凝聚操作数 (??) 允许使用一个不同的值(在右侧操作数中指定):

function getPersonScore(id: number): number {
  const person: Person = getPerson(id);
  return person.score ?? 0;}

nullish凝聚运算符比使用person.score || 0 更加稳健,因为对于任何错误的值都会返回0 ,而不仅仅是nullundefined

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;}