TypeScript技术巡礼

67 阅读6分钟

基础类型

原始数据类型

什么是原始数据类型?MDN中说,除Object类型外其它类型的值是不可变的,这些类型称为“原始数据类型”(原始值)。

// 1. string

let firstName: string = 'viking' 
let message: string = `Hello, ${firstName}`

// 2. number

let age: number = 10 
let binaryNumber: number = 0b1111

// 3. boolean

let isDone: boolean = false

// 4. undefined

let u: undefined = undefined 

// 5. null

let n: null = null

注意: undefined 和 null 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给 number 类型的变量:let num: number = undefined

Array和Tuple

说完原始数据类型,来说Object 类型,第一个要接触的就是数组,应该是我们前端开发工程师最熟悉的一种数据结构,它有多种定义方法,这里我们先简介最简单的一种。好,来到代码:

let arrOfNumbers: number[] = [1, 2, 3, 4]
arrOfNumbers.push(5)
arrOfNumbers.push('abc') //数组的项中不允许出现其他的类型

有时候需要对数组的项进行限制,固定数量和类型,这时候就可以使用元组(Tuple)。

let tupleData: [number, string] = [20, 'zhangsan'];

函数

函数定义类型

函数分为命名函数和匿名函数(函数表达式)两种,可以通过参数类型和返回值类型定义函数类型。

function add(x: number, y?: number): number {
  return x + y;
}

const add2 = function (x: number, y?: number): number {
  return x + y;
};

// x和y表示参数类型
// y?: 表示可选参数
// ):number 表示返回值类型

函数声明类型

如果仅仅声明函数格式而不定义函数体,则可以使用函数声明类型。

const add3: (x: number, y: number) => number = add2;

补充类型

此外,ts提供了补充类型用于满足特定情况。

// 1. any: 任意类型

let notSure: any = 4
notSure = 'string'

// 2. void: 函数无返回值

function print(): void {
    console.log('print message')
}

// 3. never: 函数抛出异常或永远无法结束
function error(): never {
    throw new Error('error')
}

接口

接口(interface)用于定义对象类型,描述对象的形状。通过interface,我们可以自定义任何类型和数据结构。

基本使用

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

let viking: Person = {
  name: 'viking',
  age: 20,
};

可选属性、只读属性、任意属性

interface Person {
  readonly id: number, // 只读属性
  name: string;
  age?: number; // 可选属性
  [propName: string]: any; // 任意属性,可以自由添加自定义属性
}
let viking: Person = {
  id: 10,
  name: 'viking',
  hobbies: ['basketball']
};
viking.id = 20 // 报错,只读属性不能修改

函数对象

interface可以描述函数,因为函数也是对象。interface描述函数的场景往往出现在函数上除了函数体本身还有其它属性。

interface ISum {
  (x: number, y: number): number;
  displayName: string;
}

const isum: ISum = function (x, y) {
  return x + y;
};
isum.displayName = 'isum';

鸭子类型

interface只关注对象的形式,不关注具体赋值对象的实际类型,即所谓鸭子类型(duck typing)。如果某个东西长得像鸭子,像鸭子一样游泳,像鸭子一样嘎嘎叫,那它就可以被看成是一只鸭子。

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

interface Duck {
  name: string;
  age: number;
}

const duck: Duck = {
  name: 'Peking Duck',
  age: 10,
};
const person: Person = duck; // Duck和Person的形状一样,所以Duck类型可以赋值给Person类型

接口继承

接口之间可以通过继承的方式复用已经定义好的属性类型。一个接口可以继承多个接口,创建出多个接口的合成接口。

interface Shape {
  color: string;
}

interface PenStroke {
  penWidth: number;
}

interface Square extends Shape, PenStroke {
  sideLength: number;
}

let square: Square = {
  color: 'blue',
  penWidth: 5.0,
  sideLength: 10,
};

泛型

为什么会有泛型?

考虑这样一种场景,函数中返回值的类型要和参数的类型相同,且参数的类型可能有多种,无法预先指定。

function echo(arg) {
  return arg;
}
const result = echo(123); // 此时result的类型为any

在这种类型无法预先执行且存在类型依赖的场景下,就需要一个类型占位符来表示类型,在使用的时候进行类型赋值,这个占位符就是泛型

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

上面的场景可以这样改造:

function echo<T>(arg: T): T {
  return arg;
}
const result = echo<number>(123); // 此时result类型为number

多个值的泛型

泛型就是占位符,可以传一个就可以传多个。

function swap<T, U>(tuple: [T, U]): [U, T] {
  return [tuple[1], tuple[0]];
}
const result = swap<string, number>(['str', 123]) // 此时result类型为[number, string]

泛型约束

泛型是个占位符,我们如果要求这个占位符必须包含某些属性,就可以对泛型进行约束。

interface IWithLength {
  length: number;
}

// 使用extends关键字对泛型进行约束
function echo<T extends IWithLength>(arg: T): T {
  console.log(arg.length);
  return arg;
}

const res1 = echo<number[]>([1, 2, 3]);
const res2 = echo<string>('123');
const res3 = echo({ length: 10, msg: 'hello' });

extends中还有一个条件类型约束,格式为:T extends U ? X : Y,意思是:如果T满足U的约束,则取X类型,不满足则取Y类型。条件类型类似三元表达式,可以基于泛型定义新的类型。

// 如果泛型参数 T 为 null 或 undefined,那么取 never,否则直接返回T。 
type NonType<T> = T extends null | undefined ? never : T; 
let demo1: NonType<number>; // => number 
let demo2: NonType<string>; // => string 
let demo3: NonType<null>; // => never

高级特性

字面量类型

如果我们使用const且不声明类型直接赋值,那么类型推断就会将变量推断为相应的字面量类型。

const firstName = 'viking'; // 'viking'类型
const age = 20; // 20类型

字面量类型通常用于设置一些可选值,类似枚举的功能。

type MethodType = 'GET' | 'POST' | 'PUT' | 'DELETE'
const method:MethodType = 'DELETE'

类型断言

当我们明确知道变量是什么类型时,就可以使用类型断言告诉TS不要在检查了,按我说的做。

function getLength(input: number | string) {
  const str = input as string;
  if (str.length) {
    return str.length;
  } else {
    const number = input as number;
    return number.toString().length;
  }
}

联合类型和交叉类型

交叉类型表示需要同时满足多个类型,即”AND“关系;联合类型表示满足任意一个类型即可,即”OR“关系。

interface Person {
  name: string;
  age: number;
}
type Student = Person & { stuId: string }; // 交叉类型

const stu:Student = {
  name: 'zhangsan',
  age: 20,
  stuId: '101001'
}

function getLength(input: number | string) {... // 联合类型

类型别名

对于函数类型,交叉类型,联合类型等,类型描述都比较长,此时就可以给一个类型别名,方便使用。

// type关键字定义类型别名
type Student = Person & { stuId: string }; 
type MethodType = 'GET' | 'POST' | 'PUT' | 'DELETE';

keyof和in

keyof可以获取类型成员键名组成的联合类型,而in可以对联合类型关键字进行遍历。

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

type Keys = keyof Person; // 'name'|'age'
type Student = {
  [key in Keys]: Person[key]; // Person[key]用于获取类型的成员类型(lookup types)
}

TS提供了一个内置类型Partial,可以将任意类型的属性转换为可选属性,基本原理就是使用了keyof和in。

interface Person {
  name: string;
  age: number;
}
type OptionalPerson = Partial<Person>
const p:OptionalPerson = {name: 'zhangsan'} // name和age都是可选属性

// Partial的实现原理

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