Skip to content

Typescript 扫盲

简介

文档手册

  • 兼容性:.js 文件可以直接重命名为 .ts
  • 主要提供了类型系统对 ESnext 的支持,第三方库可以编写单独的类型文件供 TypeScript 读取
  • 编译报错时终止 js 文件的生成 -> 可以在 tsconfig.json 中配置 noEmitOnError(如不配置即使静态编译报错也能生成 js)

基础

基本类型

boolean Boolean | number Number | string String | null | undefined

ts
let isDone: boolean = true // 原始数据类型
let createB: Boolean = new Boolean(true) // 对象数据类型

// void 表示没有任何返回值的函数
function alertName(): void {
  alert('My name is Tom')
}
// 申明 void 变量只能赋值 undefined null 但是
let unusable: void = undefined

// undefined 和 null 是所有类型的子类型
let u: undefined = undefined
let num: number = undefined // 但是 void 类型的变量不能赋值给 number

any

允许赋值为任意类型、访问任何属性

ts
// 对它的任何操作,返回的内容的类型都是任意值
let anyThing: any = '1212'

类型推导

定义变量时赋值 -> 会根据类型推导的规则推导出一个类型,如果定义时没有赋值 -> 将被识别为任意类型 any

ts
let myFavoriteNumber = 'seven'
myFavoriteNumber = 7 // 编译报错

let myFavoriteNumber
myFavoriteNumber = 'seven'
myFavoriteNumber = 7 // OK 的

联合类型

表示取值可以为多种类型中的一种 string | number

ts
let myFavoriteNumber: string | number

// 在还没确定类型时 -> 只能访问 联合类型 的所有类型里共有的属性
myFavoriteNumber.length // 编译报错
myFavoriteNumber.toSting() // OK 的

// 赋值后会推导出类型 -> 可以访问相应的属性
myFavoriteNumber = 'seven'
myFavoriteNumber = 7

对象的类型 - 接口

  • 接口是对类(classes)的一部分行为进行抽象,也能对对象的形状(Shape)进行描述
  • 有一些内置对象的接口定义 IArguments, NodeList, HTMLCollection
ts
// 接口一般首字母大写
interface Person {
  readonly id: number
  name: string
  age?: number
  [propName: string]: any
}
// - readonly 表示该属性是只读的,定义对象之后,不能修改该属性
// - age 后的 ? 表示该属性是可选的
// - [propName: string] 定义了可以有任意 string 类型的属性 key
//   - 同时确定了该接口的确定属性和可选属性的值都必须是它的类型(any)的子集

// 约束对象的 Shape
let tom: Person = {
  id: 89757,
  name: 'Tom',
  age: 25,
  gender: 'male',
}

数组的类型

  • 「类型 + 方括号」表示法
ts
let fibonacci: number[] = [1, 1, 2, 3, 5]
// [] 元素是 any
let list: any[] = ['tangdw', 25, { website: 'http://tangdw.com' }]
  • 数组泛型法
ts
let fibonacci: Array<number> = [1, 1, 2, 3, 5]
  • 接口表示法
ts
interface NumberArray {
  [index: number]: number
}
// 类似对象 key 是 number value 也是 number
let fibonacci: NumberArray = [1, 1, 2, 3, 5]

函数的类型

ts
// 定义了参数个数、类型,返回类型, ? 表示这个参数是可选的(放在最后面
function sum(x: number, y: number, z?: string): number {
  return x + y
}

// 实际上是对右边的匿名函数定义了类型,而 sum 的类型是推导出来的
let sum = function (x: number, y: number): number {
  return x + y
}

// sum: (x: number, y: number) => number 这样才是对 sum 的定义 -> -_- 麻烦了一点
let sum: (x: number, y: number) => number = function (x: number, y: number): number {
  return x + y
}

// 亦可以用接口来定义
interface GetSum {
  (x: number, y: number): number
}
let sum: GetSum
sum = function (x: number, y: number): number {
  return x + y
}

// 剩余参数 ...items 可以用 any[] 类型来定义它
function push(array: any[], ...items: any[]) {
  items.forEach(function (item) {
    array.push(item)
  })
}

// 重载
function reverse(x: number): number
function reverse(x: string): string
function reverse(x: number | string): number | string {
  if (typeof x === 'number') {
    return Number(x.toString().split('').reverse().join(''))
  } else if (typeof x === 'string') {
    return x.split('').reverse().join('')
  }
}
// ts 优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面
// 这样的函数输入可以是 number 或 string,输入 number 就返回 number,输入 string 就返回 string

类型断言

<类型>变量 / 变量 as 类型 (在 tsx 中只能用后一种

ts
function getLength(something: string | number): number {
  // 这里用 <string> 断言 something 不然编译会报错(因为 length 不是 string | number 的共有属性
  if ((<string>something).length) {
    return (<string>something).length
  } else {
    return something.toString().length
  }
}
ts
// Type 'readonly [10, 20]' 只读类型
let y = [10, 20] as const

声明文件

使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能 @types/xxx

ts
// src/jQuery.d.ts

// declare 声明语句 -> 声明 window 下的全局变量
declare var jQuery: (selector: string) => any
// >> 一般来说,全局变量都是一个常量,不允许再修改它的值了,所以不会用 var 或 let 都用 const

declare function jQuery(selector: string): any

// namespace 用于指定命名空间(对象
declare namespace jQuery {
  // 可以直接使用 interface 或 type 来声明一个全局的接口或类型(前面不需要 declare
  interface AjaxSettings {
    method?: 'GET' | 'POST'
    data?: any
  }
  function ajax(url: string, settings?: AjaxSettings): void
}
// 以上声明会不冲突的合并起来 -_-

// 导出
// 只有 function、class 和 interface 可以直接默认导出,其他的变量需要先定义出来,再默认导出

声明文件 -> 把声明语句放到一个单独的文件(jQuery.d.ts)中 | 书写声明文件

ts
// src/index.ts

jQuery('#foo')

// jQuery. 声明文件的命名空间
let settings: jQuery.AjaxSettings = {
  method: 'POST',
  data: {
    name: 'foo',
  },
}

jQuery.ajax('/api/post_something', settings)

内置对象

  • Boolean Error Date RegExp MDN

  • DOM BOM 的内置对象 Document HTMLElement Event NodeList lib

  • TypeScript 自动包含了核心 lib 无需引入,参数类型自动推断

  • 但是 node 的没包含 需要 npm install @types/node --save-dev

进阶

类型别名 给一个类型起个新名字

type 自定义类型(别名

ts
type Name = string
type NameResolver = () => string

type NameOrResolver = Name | NameResolver

function getName(n: NameOrResolver): Name {
  if (typeof n === 'string') {
    return n
  } else {
    return n()
  }
}

字符串字面量类型

type 自定义类型

ts
// 用 type 定义 EventNames 类型 只能是这三种字符串中的一个
type EventNames = 'click' | 'scroll' | 'mousemove'

function handleEvent(ele: Element, event: EventNames) {
  // .
}

handleEvent(document.getElementById('world'), 'dbclick') // 报错,event 不能为 'dbclick'

handleEvent(document.getElementById('hello'), 'scroll') // OK 的

元组

相对于数组来说,是一个由不同类型元素组合组成的集合

ts
// 初始化可以不赋值,但是赋值必须提供所有元组类型中指定的项
let xcatliu: [string, number] = ['Xcat Liu', 25]

// 后面再追加元素时(越界),只能加已定义的联合类型
xcatliu.push(true) // 编译报错

枚举 Enum

取值被限定在一定范围内,比如一周只能有七天、颜色限定为红绿蓝

ts
enum Days {
  Sun,
  Mon,
  Tue,
  Wed,
  Thu,
  Fri,
  Sat,
}

// 成员会被赋值为从 0 开始递增的数字,同时也会对 枚举值 -> 枚举名 进行反向映射
Days['Mon'] === 1 // true
Days[1] === 'Mon' // true

// 给枚举项手动赋值
enum Days {
  Sun = 3,
  Mon = 1,
  Tue,
  Wed,
  Thu,
  Fri,
  Sat = 'red'.length,
}
// 其中未被赋值的 Tue Wed 依次递增(步长 1) 2 3
// Wed 3 覆盖前面手动定义的 Sun 不会报错,但是最好避免这种情况
// Sat 计算所得项,如果它后面是一个未手动赋值的成员 -> 编译报错
  • 常数枚举 const enum 在编译阶段会被删除,不能包含计算成员
ts
const enum Directions {
  Up,
  Down,
  Left,
  Right,
}

let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]
// 👇
var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */]
  • 外部枚举 declare enum 常出现在声明文件 xx.d.ts

在 ES5 通过构造函数实现类的概念,通过原型链实现继承。ts 在 ES6 class 之外还添加了一些新的用法

  • ES6 存取器 使用 getter 和 setter 可以改变属性的赋值和读取行为
js
class Animal {
  constructor(name) {
    this.name = name
  }
  get name() {
    return 'Jack'
  }
  set name(value) {
    console.log('setter: ' + value)
  }
}

let a = new Animal('Kitty') // setter: Kitty
a.name = 'Tom' // setter: Tom
console.log(a.name) // Jack
  • ts 修饰符 public private protected

    • public 公有的,可以在任何地方被访问到。默认所有的属性方法都是 public
    • private 私有的,不能在声明它的类之外访问
    • protected 受保护的,和 private 类似,区别是它在子类中中是允许被访问的
  • 抽象类 abstract

ts
// 声明抽象类,不允许被实例化
abstract class Animal {
  public name
  public constructor(name) {
    this.name = name
  }
  public abstract sayHi()
}

let a = new Animal('Jack') // 编译报错

// 由子类去继承并实现抽象方法
class Cat extends Animal {
  // 报错...不允许的
  // public eat() {
  //   console.log(`${this.name} is eating.`);
  // }
  public sayHi() {
    console.log(`Meow, My name is ${this.name}`)
  }
}

// OK 的
let cat = new Cat('Tom')

类与接口

实现implements 一个类去实现某个接口

ts
interface Alarm {
  alert()
}

interface Light {
  lightOn()
  lightOff()
}

// 接口继承接口
interface LightableAlarm extends Alarm {
  lightOn()
  lightOff()
}

class Door {}

class SecurityDoor extends Door implements Alarm {
  alert() {
    console.log('SecurityDoor alert')
  }
}

// 实现多个和接口用 , 分隔
class Car implements Alarm, Light {
  alert() {
    console.log('Car alert')
  }
}

// 接口继承类
interface LangCar extends Car {
  len: number
}

泛型

在定义接口、函数或类的时候,不预先指定具体的类型,而是在使用的时候再指定类型

ts
// 在函数名后添加 <T> 其中 T 代表任意输入的类型 (value)
function createArray<T>(length: number, value: T): Array<T> {
  // T[] <-> Array<T> 泛型
  let result: T[] = []
  for (let i = 0; i < length; i++) {
    result[i] = value
  }
  return result
}

createArray<string>(3, 'x') // ['x', 'x', 'x']

// 也可以不指定 T 让类型推导自动推导出来 -_-
createArray(3, 'x') // ['x', 'x', 'x']

一次定义多个类型参数

ts
// tuple: [T, U] 定义形参 tuple 是一个元组类型
function swap<T, U>(tuple: [T, U]): [U, T] {
  return [tuple[1], tuple[0]]
}

swap([7, 'seven']) // ['seven', 7]

泛型约束

js
interface Lengthwise {
  length: number;
}

// T extends Lengthwise 约束传入的参数必须包含 length 属性
function loggingIdentity<T extends Lengthwise, U>(arg: T, value: U): T {
  console.log(arg.length);
  return arg;
}

泛型接口

ts
interface CreateArrayFunc<T> {
  (length: number, value: T): Array<T>
}

let createArray: CreateArrayFunc<any> // 定义泛型的类型
createArray = function <T>(length: number, value: T): Array<T> {
  let result: T[] = []
  for (let i = 0; i < length; i++) {
    result[i] = value
  }
  return result
}

createArray(3, 'x') // ['x', 'x', 'x']

泛型类

ts
class GenericNumber<T> {
  zeroValue: T
  add: (x: T, y: T) => T
}

// 定义类型 number
let myGenericNumber = new GenericNumber<number>()
myGenericNumber.zeroValue = 0
myGenericNumber.add = function (x, y) {
  return x + y
}

指定泛型参数的默认类型

ts
// T = string 指定默认类型是 string 当无法推断的时候就会起作用
function createArray<T = string>(length: number, value: T): Array<T> {
  let result: T[] = []
  for (let i = 0; i < length; i++) {
    result[i] = value
  }
  return result
}

声明合并

如果定义了两个相同名字的函数、接口或类,那么它们会合并成一个类型 -> 取并集