这一次,彻底掌握TypeScript(三)断言与类型别名

297 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情

这是我彻底掌握 TypeScript 的第三篇,这次我们主要分享 TypeScript 中的断言与类型别名,点击下面链接可以查看之前文章。

往期文章:

这一次,彻底掌握TypeScript(一)基本类型&语法

这一次,彻底掌握TypeScript(二)接口与类

一、断言

1、类型断言

有时候你会遇到这样的情况,你会比 TypeScript 更了解某个值的详细信息。通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。 通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。类型断言好比其他语言里的类型转换,但是不进行特殊的数据检查和解构。它没有运行时的影响,只是在编译阶段起作用。 类型断言有两种形式:

1)“尖括号”语法

let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;

2)as语法

let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;

2、非空断言

在上下文中当类型检查器无法断定类型时,一个新的后缀表达式操作符 ! 可以用于断言操作对象是非 null 和非 undefined 类型。具体而言,x! 将从 x 值域中排除 null 和 undefined。 那么非空断言操作符到底有什么用呢?下面我们先来看一下非空断言操作符的一些使用场景:

1)忽略undefined和null类型

function myFunc(maybeString: string | undefined | null) {
  // Type 'string | null | undefined' is not assignable to type 'string'.
  // Type 'undefined' is not assignable to type 'string'.
  const onlyString: string = maybeString; // Error
  const ignoreUndefinedAndNull: string = maybeString!; // Ok
}

2)调用时忽略undefined类型

type NumGenerator = () => number;

function myFunc(numGenerator: NumGenerator | undefined) {
  // Object is possibly 'undefined'.(2532)
  // Cannot invoke an object which is possibly 'undefined'.(2722)
  const num1 = numGenerator(); // Error
  const num2 = numGenerator!(); // OK
}

因为 ! 非空断言操作符会从编译生成的 JavaScript 代码中移除,所以在实际使用的过程中,要特别注意。比如下面这个例子:

const a: number | undefined = undefined;
const b: number = a!;
console.log(b);

以上TS代码会编译生成以下ES5代码:

"use strict";
const a = undefined;
const b = a;
console.log(b);

虽然在 TS 代码中,我们使用了非空断言,使得 const b: number = a!; 语句可以通过 TypeScript 类型检查器的检查。但在生成的 ES5 代码中,! 非空断言操作符被移除了,所以在浏览器中执行以上代码,在控制台会输出 undefined。

3、确定赋值断言

在 TypeScript 2.7 版本中引入了确定赋值断言,即允许在实例属性和变量声明后面放置一个 ! 号,从而告诉 TypeScript 该属性会被明确地赋值。为了更好地理解它的作用,我们来看个具体的例子:

let x: number;
initialize();
// Variable 'x' is used before being assigned.(2454)
console.log(2 * x); // Error

function initialize() {
  x = 10;
}

很明显该异常信息是说变量 x 在赋值前被使用了,要解决该问题,我们可以使用确定赋值断言:

let x!: number;
initialize();
console.log(2 * x); // Ok

function initialize() {
  x = 10;
}

通过 let x!: number; 确定赋值断言,TypeScript 编译器就会知道该属性会被明确地赋值。

二、类型别名

TypeScript 引入了类型别名 type,其作用就是给类型起一个新名字,可以作用于原始值(基本类型)、联合类型、元组以及其它任何你需要手写的类型:

type Second = number; // 基本类型
let timeInSecond: number = 10;
let time: Second = 10;  // time的类型其实就是number类型
type userOjb = {name:string} // 对象
type getName = ()=> string  // 函数
type data = [number,string] // 元组
type numOrFun = Second | getName  // 联合类型

需要注意的是起别名不会新建一个类型,它创建了一个新名字来引用那个类型。给基本类型起别名通常没什么用。类型别名常用于联合类型。

1、type的应用和interface的区别

1)和接口一样,用来描述对象或函数的类型

type User = {
  name: string
  age: number
};
type SetUser = (name: string, age: number) => void;

在 ts 编译成 js 后,所有的接口和 type 都会被擦除掉。

2)扩展和实现

接口可以扩展,但 type 不能 extends 和 implement,但是 type 可以通过交叉类型实现 interface 的 extends 行为。interface 可以 extends type,同时 type 也可以与 interface 类型交叉,举个🌰:

// interface 扩展 type
interface Name {
  name: string;
}
interface User extends Name {
  age: number;
}
let stu: User = { name:'wang', age: 10 }

// 上面的扩展可以用type交叉类型来实现
type Name = {
  name: string;
}
type User = Name & { age: number  };
let stu: User = { name: 'wang', age: 1 };
console.log(stu) // { name: 'wang', age: 1 }

3)接口的声明合并

接口可以定义多次,并将被视为单个接口(即所有声明属性的合并)。而 type 不可以定义多次。

interface User {
  name: string
  age: number
}

interface User {
  sex: string
}
let user: User = { name: 'wang',age: 1,sex: 'man' }

4)映射类型

type 如果定义的是联合类型能使用 in 关键字生成映射类型,但 interface 不行。

type Keys = "name" | "sex"

type DulKey = {
  [key in Keys]: string    // 类似for...in
}

let stu: DulKey = {
  name: "wang",
  sex: "man"
}

参考资料

极客时间《TypeScript开发实战》专栏

《深入理解TypeScript》

typeScript 中的type关键字

一份不可多得的 TS 学习指南(1.8W字)

Typescript使用手册