TypeScript入门指南

1,759 阅读5分钟

Array 数组

在TypeScript中定义数组有两种方式

// 第一种方式是可以在元素类型后面接上[]
const listOne: number[] = [1, 2, 3]

// 第二种方式是数组泛型,Array[元素类型]
const listTwo: Array<number> = [4, 5, 6]

Assertion 类型断言

类型断言分作两种形式

// 第一种用 "尖括号"
const stringOne: any = 'hello TypeScript'
const stringOneLength: number = (<string> stringOne).length


// 第二种用 关键字 “as”
const stringTwo: any = 'hello TypeScript'
const stringTwoLength: number = (stringTwo as string).length

tips: 在TypeScript的JSX中类型断言可以使用as,而不允许使用尖括号方式

Enum 枚举

使用枚举可以清晰地表达我们的意图,比如 订单的状态开始是0,未结账是1,运输中是2,运输完成是3,已收货是4

数字枚举

使用数字枚举来完成订单的状态

enum OrderStatus {
  START,
  UNPAID,
  SHIPPING,
  SHIPPED,
  COMPLETE
}

console.log(
  OrderStatus.START,     // 0
  OrderStatus.UNPAID,    // 1
  OrderStatus.SHIPPING,  // 2
  OrderStatus.SHIPPED,   // 3
  OrderStatus.COMPLETE,  // 4
)

tips: 举可以让我们定义一些名字有意义的常量, 使用枚举可以清晰地表达我们的意图

字符串枚举

enum OrderStatus {
  START    = 'START',
  UNPAID   = 'UNPAID',
  SHIPPING = 'SHIPPING',
  SHIPPED  = 'SHIPPED',
  COMPLETE = 'COMPLETE'
}

console.log(
  OrderStatus.START,     // START
  OrderStatus.UNPAID,    // UNPAID
  OrderStatus.SHIPPING,  // SHIPPING
  OrderStatus.SHIPPED,   // SHIPPED
  OrderStatus.COMPLETE,  // COMPLETE
)

tips: 由于字符串枚举没有递增的含义,字符串枚举成员都必须手动初始化, 字符串枚举能进一步提升可读性,且在运行时中提供具有刻度性质的值

反向映射

enum Test {
  PRAISE
}

console.log(
  TEST.PRAISE, // 0
  TEST[0]      // 'PRAISE'
)

// 原因: 编译器拿到这段代码会编译到JavaScript,是如下这样的
(function (Enum) {
  Enum[Enum['PRAISE'] = 0] = 'PRAISE'
})(Enum || (Enum = {}))

tips: 在字符串枚举中没有反向映射

Symbol

symbol成为一种新的原生类型,就像基本类型 numberstring 一样

const symbolOne = Symbol('hello')
const symboTwo = Symbol('hello')

symbolOne === symbolTwo // false

tips: 通过同样的方式生成两个symbol,也是不同的,因为symbol是唯一的

Generic 泛型

泛型用于提升代码的重用性我们希望自己编写的代码,无论是模块还是组件,不仅能支持当前设计的数据类型,而且也能支持将来的数据类型。这在大型系统中是非常基础且重要的功能。所以我们常常能在各种各样的静态类型语言中看到泛型设计,使得用户可以灵活地选择希望支持的类型,而非局限于某一种类型

Fcuntion 泛型函数

function hello<T>(arg:T): T {
  return arg
}
console.log(
  hello('string'),
  hello(23),
  hello({name: 'Praise'})
)
// string 23 { name: 'Praise' }

tips: 泛型变量T,T代表用户即将传入的类型。类型既可以是number,也可以是string,而在最后,我们还使用T作为返回值的类型。这就达到了返回 值和参数类型相同的目的,保持住了函数表达的准确性

使用泛型函数 有两种方法可以选择

// 第一种 使用熟悉的尖括号方式进行表达
const optput = hello<string>('hello TypeScript')

// 第二种 使用类型推断
const optput = hello('hello TypeScript')

Variable 泛型变量

function hello<T>(arg: T): T {
 console.log(arg.length) //  error TS2339: Property 'length' does not exist on type 'T'.
  return arg
}

tips: 编译器会非常迅速地进行报错,告诉我们泛型T并没有length这个属性

可以使用泛型数组来表达这样的情况

// 中括号 表达
function hello<T>(args: T[]): T[] {
  console.log(args.length)
  return args
}

// Array 表达
function hello<T>(args: Array<T>): Array<T> {
  console.log(args.length)
  return args
}

Interface 高级类型

一个很常用的场景,比如函数传参

interface IPraise {
  name: string
  age: number
  hobby: Array<string>
}

function person(arg: IPraise): IPraise {
  return arg
}

console.log(
  person({name: 'Praise', age: 23, hobby: ['code']})
)

可选属性

将一个已知类型的每个属性都变为可选的,比如像这样使用问号

interface IPraise {
  name?: string
  age?: number
  hobby?: Array<string>
}

tips: 在实例化IPraise时,我们不必给每个属性都赋值

只读属性

interface IPraise {
  readonly name: string
  readonly age: number
  readonly hobby: Array<string>
}

索引签名

在不确定属性数量的情况下可以使用索引签名来添加一些额外的属性

interface IPraise {
  name: string
  age: number
  hobby: Array<string>
  [propName: string]: any
}

tips: IPraise的属性值都是只读不可修改的

交叉类型与联合类型

type NewType = number & string
let newType: NewType

interface IPraiseOne {
  name: string
  age: number
}

interface IPraiseTwo {
  hobby: Array<string>
}

type Praise = IPraiseOne | IPraiseTwo
let praise: Praise

映射类型

在映射类型里,新类型以相同的形式去转换旧类型里的每个属性。例如,你可以令每个属性成为只读类型或可选类型

type Readonly<T> = {
  readonly [P in keyof T]: T[p]
}

type Partial<T> = {
  [P in keyof T]?: T[P]
}

type PersonPartial = Partial<Person>
type ReadonlyPerson = Readonly<Person>

tips: 内置的类型还有Required、Pick、Record、Exclude、Extract、NonNullable;读者可以自己尝试这些类型方法,它们的实现都在typescript/lib/lib.es5.d.ts中

// 一个Lodash中常见的pluck函数,就是从对象中选取属性的子集. 下面是pluck函数的简化版示例
function pluck(obj, names) {
  return names.map(name => obj[name])
}

如果需要在TypeScript中使用这个函数,要通过索引类型查询索引访问操作符:

function pluck<T, K extends keyof T>(obj: T, names: K[]): T[K][] {
  return names.map(name => obj[name])
}

interface Person {
  name: string
  age: number
}

const person: Person = {
  name: 'Praise',
  age: 23
}

pluck(person, ['name'])  // Praise

tips: 编译器会检查传入的值是否是Person属性的一部分

让我们解释一下上面代码的意义

  1. 泛型: 这里有T和K两种类型。根据类型推断,第一个参数obj就是person,类型会被推断为Person
  2. 类型推断: 第二个参数,我们可以从右往左进行阅读。keyof关键字可以获取T,也就是Person的所有属性名,即['name','age']
  3. 类型索引: extends关键字让泛型K继承了Person的所有属性名,即['name','age'],依托于keyof关键字完成了类型索引
  4. 索引访问操作符: 返回值的类型是T[K][],阅读起来有点困难。它实际表述的意思是,变量T取属性K的值的数组,其中T[K]就是索引访问操作符