TypeScript类型整理

136 阅读9分钟

TypeScript 是微软开发一款开源的编程语言,它是 JavaScript 的一个超集,本质上是为 JavaScript 增加了静态类型声明。任何的 JavaScript 代码都可以在其中使用,不会有任何问题。TypeScript 最终也会被编译成 JavaScript,使其在浏览器、Node 中等环境中使用。

优点

TypeScript 相比于 JavaScript 具有以下优势:

  • 更好的可维护性和可读性
  • 引入了静态类型声明,不需要太多的注释和文档,大部分的函数看类型定义就知道如何使用了
  • 在编译阶段就能发现大部分因为变量类型导致的错误

TypeScript 包含了 JavaScriptall,即使是仅仅将 .js 改成 .ts,不修改任何的代码都可以运行。

基本类型
boolean
var isFetching: boolean = false;
number

这里指的是IEEE 754下的浮点型

数值型number包含了InfinityNaN

var luckyNumber: number = 10;
var notSoLuckyNumber: number = NaN;
string
var myName: string = 'Preethi';
null
var data: null = null;
void

在这里void描述的是JavaScript中undefined类型, 注意要将nullundefined区别对待。由于undefinednull是不同的类型,而void类型应该属于undefined类型,因此Flow会抛出一个错误。

var data: void = undefined;
Array

JavaScript中的数组类型。使用Array<T>这样的语法来定义一个数组,其中数组的元素类型为T

var messages: Array<string> = ['hello', 'world', '!']; // 用string替换了T,表示messages是一个字符串数组。泛型const me: string[] = ['axuebin', '27']; // 定义一个都是 string 的数组
  1. tuple元组

需要定义一个已知元素和类型的数组,但是各个元素的类型不相同,可以使用 tuple 元组 来定义:

 const me: [string, number, boolean] = ['axuebin', 27, true];

当我们想要在这个数组 push 一个新元素时,会提示 (string | number | boolean),这是表示元组额外增加的元素可以是之前定义的类型中的任意一种类型。(string | number | boolean) 称作联合类型

  1. eunm枚举

枚举是 TSJS 标准数据类型的补充,Java/c 等语言都有枚举数据类型,在 TypeScript 里可以这样定义一个枚举:

    enum Animal {
      Cat,
      Dog,
      Mouse,
    }
    const cat: Animal = Animal.Cat; // 0
    const dog: Animal = Animal. Dog; // 1
Object

JavaScript中的对象类型。有几种不同的方式来为对象添加类型限制。

你可以添加类型来描述对象的格式:

var aboutMe: { name: string, age: number } = {
  name: 'Preethi',
  age: 26,
};

你可以用对象来作为Map,并给键和值都设置类型:

var namesAndCities: { [name: string]: string } = {
  Preethi: 'San Francisco',
  Vivian: 'Palo Alto',
};

你也可以仅仅定义一个对象为Object类型:

var someObject: Object = {};someObject.name = {};
someObject.name.first = 'Preethi';
someObject.age = 26;

上面这段代码使你可以不受限制得设置对象的键和值,因此就类型检查而言,这种方式并没有增加太多的价值。

any

它可以代表任何类型。any类型一定程度上避免了类型检查,因此如非必要,尽量扁面使用这个类型。

var iCanBeAnything:any = 'LALA' + 2; // 'LALA2'

有一个场景下比较适用:当你使用了一个扩展了系统原型的外部类库(类似Object.prototype)。 例如,你使用的类库为Object.prototype扩展了一个叫doSomething的属性。

Object.prototype.someProperty('something');

代码很可能会报如下错误:

41:   Object.prototype.someProperty('something')
                       ^^^^^^ property `someProperty`. Property not found in
41:   Object.prototype.someProperty('something')
      ^^^^^^^^^^^^ Object

为了避免着各种情况,你可以使用any类型:

(Object.prototype: any).someProperty('something'); // No errors!
Function

给方法添加类型的最常见的用法是,为该方法的参数和返回值添加类型检查:

var calculateArea = (radius: number): number => {
  return 3.14 * radius * radius
};

你甚至可以给async方法和生成器(generator)添加类型:

async function amountExceedsPurchaseLimit(// amountExceedsPurchaseLimit方法本身被声明会返回一个Promise对象
  amount: number,
  getPurchaseLimit: () => Promise<number> // 将第二个参数getPurchaseLimit声明为一个返回Promise对象的函数
): Promise<boolean> {
  var limit = await getPurchaseLimit();
​
  return limit > amount;
}
类型别名(Type alias)

类型别名(Type alias)允许你使用已有的类型(number,、string等)来组合成一个新的类型:

type PaymentMethod = {// 创建了一个叫作PaymentMethod的新类型,包含了number和string两种类型。
  id: number,
  name: string,
  limit: number,
};
// PaymentMethod使用
var myPaypal: PaymentMethod = {
  id: 123456,
  name: 'Preethi Paypal',
  limit: 10000,
};
​
// 给原始类型包裹一层新类型,你可以为它们创建一个新的类型别名,这样可以清楚的表明,Name和Email是指代不同的事物,而不仅仅是一个字符串。由于Name和Email是不可互换的,这么做可以帮助你避免混淆它们。
type Name = string;
type Email = string;
​
var myName: Name = 'Preethi';
var myEmail: Email = 'iam.preethi.k@gmail.com';
泛型(Generics)

泛型是一种对类型本身进行抽象的方法。

// 为类型T创建了一个抽象的概念,你可以使用任何类型来代替T。对于numberT来说,T是number类型的;而对于arrayT来说,T是Array<number>类型的
type GenericObject<T> = { key: T };
​
var numberT: GenericObject<number> = { key: 123 };
var stringT: GenericObject<string> = { key: "Preethi" };
var arrayT: GenericObject<Array<number>> = { key: [1, 2, 3] }
Maybe

Maybe类型允许我们声明一个包含nullundefined两个潜在类型的值。对于类型TTnullundefined三种类型,意味着一个变量可能是Tnullundefined三者之一。在类型定义前加上一个“?”就可以定义一个Maybe类型:

var message: ?string = null; // 表示message是string类型、null或undefined// 也可以用Maybe类型来表示一个对象属性可能是某种类型T或者undefined
type Person = {
  firstName: string,
  middleInitial?: string, // 将“?”放在属性名middleInitial之后,你可以表明这个对象时可选的
  lastName: string,
};
Disjoint unions(或操作)

这是创建数据模型的另一个强大的方法。当程序需要同时处理不同的数据类型,Disjoint unions会是一个很有用的方法。换句话说,根据环境的不同,数据的结构也会不同。

type PaymentMethod = Paypal | CreditCard | Bank;
高级类型
联合类型
let a: string | number = '123' // 变量a的类型既可以是string,也可以是number
a = 123
keyof

将一个类型的属性名全部提取出来当做联合类型

// 1. 定义一个接口
interface Person {
  name: string
  age: number
}

type PersonKeys = keyof Person // 等同于 type PersonKeys = 'name' | 'age'

const p1: PersonKeys = 'name' // 可以
const p2: PersonKeys = 'age' // 可以
const p3: PersonKeys = 'height' // 不能将类型“"height"”分配给“name | age”
高级类型
联合类型
let a: string | number = '123' // 变量a的类型既可以是string,也可以是number
a = 123
keyof

将一个类型的属性名全部提取出来当做联合类型

// 1. 定义一个接口
interface Person {
  name: string
  age: number
}

type PersonKeys = keyof Person // 等同于 type PersonKeys = 'name' | 'age'

const p1: PersonKeys = 'name' // 可以
const p2: PersonKeys = 'age' // 可以
const p3: PersonKeys = 'height' // 不能将类型“"height"”分配给“name | age”
Record

Record用于属性映射, 实现原理是Record定义,接收两个泛型参数:

type Record<K extends string | number | symbol, T> = {
    [P in K]: T;
}
  • 定义一个普通的对象类型
type Person = Record<string, string>;
const person1: Person = {['name']: 'yaya'}
  • 搭配联合类型用法
type TypeItem = 'name' | 'sex' | 'age';

type Person = Record<TypeItem, string>;
const person1: Person = {['name']: 'yaya'}
  • 同样可以映射对象,让对象的每个属性都是一个拥有特定键值对的类型
type TypeItem = {
  name: string;
  nickName: string;
};

type Person = Record<string, TypeItem>;
const person1: Person = {name: {name: 'yaya', nickName: 'yayaw2'}}
Pick

可以选择一个原来的接口中一部分的属性定义

type Person {
  name: string;
  age: number;
}

type NewPerson = Pick<Person, 'sex'>;

type NewPerson2 = Pick<Person, 'sex' | 'weight'>;
Readonly

让一个定义中的所有属性都变成只读参数

type Person {
  name: string;
  age: number;
}
type NewPerson = Readonly<Person>;

const yaya: NewPerson = {
	name: 'yaya',
  age: 18,
};

// 报错,age为只读属性
yaya.age = 19;
Exclude(排除)

可以排除联合类型中一部分的内容,注意Exclude是操作联合类型的

type TypeItem = 'name' | 'sex' | 'age';
type Person = Exclude<'name'>;
const yaya: Person = 'name'; // 报错,提示不能将'name'分配给'sex' 与 'age'
Omit (省略的)

将定义的类型的键值对删除一部分

type Person {
  name: string;
  age: number;
}
type NewPerson = Omit<Person, 'name'>; // NewPerson中只有age属性
Partial (部分的)

让一个定义中的所有属性都变成可选参数

// 定义一个Person接口
interface Person {
  name: string
  age: number
}

// 但是我们有可能数据是请求过来的,刚开始我们需要定义一个空对象,如下。
const person1: Person = {}
/**
  但是这样就加粗样式会出现报错,类型“{}”缺少类型“Person”中的以下属性: name, age。
  可能我们可以更改定义方式如下,但是有的时候我们不想破坏事先的定义,或者不能破坏
  interface Person {
    name?: string
    age?: number
  }
*/

/**
  那这个时候我们就可以用到typescript自带的高级类型 Partial,就相当于将上方接口所有属性变成可选的
  将我们需要定义的类型当做泛型传入Partial中,那么就相当于当前的类型里面的所有属性都是可选的
 */
const person2: Partial<Person> = {} // 可以
const person3: Partial<Person> = { name: 'xiaodu' } // 可以
const person4: Partial<Person> = { height: 1.88 } // 报错 “height”不在类型“Partial<Person>”中
Required

和Partial相反,是将一个定义中的属性全部变成必选参数

type Person {
  name?: string;
  age?: number;
}

// 报错,提示缺少age属性
const personA : Required<Person> = {name: 'yaya'}
ReadonlyArray(只读数组)

创建一个数组,数组中的索引不允许被修改。

当我们使用const创建对象或者数组时,其实是可以修改其内部属性的,但是有的时候我们可能不需要其内部能够被修改:

const list: number[] = [1, 2, 3, 4, 5];
list[0] = 3;

有两种解决方式:

  1. 通过类型断言的方式
const list: number[] = [1, 2, 3, 4, 5] as const;
list[0] = 3; //报错
  1. 使用ReadonlyArray
const list: ReadonlyArray<number> = [1, 2, 3, 4, 5];
list[0] = 3; //报错

as constReadonlyArray这两者的区别在哪里?

区别在于as const是深层次的,如果数组内放的对象,对象内部数据也是不能被修改的。ReadonlyArray则是‘浅层’的。

interface 和 type 关键字

interface 和 type 两个关键字因为其功能比较接近,应该在什么时候用 type,什么时候用 interface? interface 的特点如下:

  • 同名 interface 自动聚合,也可以和已有的同名 class 聚合,适合做 polyfill(填充)
  • 自身只能表示 object/class/function 的类型

与 interface 相比,type 的特点如下:

  • 表达功能更强大,不局限于 object/class/function
  • 要扩展已有 type 需要创建新 type,不可以重名
  • 支持更复杂的类型操作

基本上所有用 interface 表达的类型都有其等价的 type 表达。但在实践的过程中,也发现了一种类型只能用 interface 表达,无法用 type 表达,那就是往函数上挂载属性。