Skip to content

TypeScript 笔记

基础

原始数据类型

ts
let isDone: boolean = false;
let decLiteral: number = 6;
let myName: string = 'Tom';
let unusable: void = undefined;//声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefined 和 null
let u: undefined = undefined;
let n: null = null;

与 void 的区别是,undefined 和 null 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给 number 类型的变量: ##任意值 如果是一个普通类型,在赋值过程中改变类型是不被允许的,但如果是 any 类型,则允许被赋值为任意类型 在任意值上访问任何属性都是允许的,也允许调用任何方法可以认为,声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值 变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型:

any 在数组中的应用

一个比较常见的做法是,用 any 表示数组中允许出现任意类型:

ts
let list: any[] = ['xcatliu', 25, { website: 'http://xcatliu.com' }];

联合类型

ts
let myFavoriteNumber: string | number;

在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型,接口一般首字母大写 定义的变量比接口少了一些属性是不允许的,多一些属性也是不允许的

可选属性

有时我们希望不要完全匹配一个形状,那么可以用可选属性:

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

let tom: Person = {
    name: 'Tom'
};

数组

###「类型 + 方括号」表示法

ts
let fibonacci: number[] = [1, 1, 2, 3, 5];

数组泛型

ts
let fibonacci: Array<number> = [1, 1, 2, 3, 5];

arguments 是一个类数组对象(Array-like Object),它包含了函数调用时传入的所有参数

函数的类型

函数声明

一个函数有输入和输出,要在 TypeScript 中对其进行约束,需要把输入和输出都考虑到,其中函数声明的类型定义较简单:

ts
function sum(x: number, y: number): number {
    return x + y;
}

函数表达式

如果要我们现在写一个函数表达式(Function Expression)的定义,可能会写成这样:

ts
let mySum = function (x: number, y: number): number {
    return x + y;
};

这是可以通过编译的,不过事实上,上面的代码只对等号右侧的匿名函数进行了类型定义,而等号左边的 mySum,是通过赋值操作进行类型推论而推断出来的。如果需要我们手动给 mySum 添加类型,则应该是这样:

ts
let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
    return x + y;
};

注意不要混淆了 TypeScript 中的 => 和 ES6 中的 =>。

在 TypeScript 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。

用接口定义函数的形状

我们也可以使用接口的方式来定义一个函数需要符合的形状:

ts
interface SearchFunc {
    (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
    return source.search(subString) !== -1;
}

采用函数表达式|接口定义函数的方式时,对等号左侧进行类型限制,可以保证以后对函数名赋值时保证参数个数、参数类型、返回值类型不变。

剩余参数

ES6 中,可以使用 ...rest 的方式获取函数中的剩余参数(rest 参数):

ts
function push(array, ...items) {
    items.forEach(function(item) {
        array.push(item);
    });
}

let a: any[] = [];
push(a, 1, 2, 3);

类型断言

类型断言(Type Assertion)可以用来手动指定一个值的类型。我们确实需要在还不确定类型的时候就访问其中一个类型特有的属性或方法

语法

值 as 类型

联合类型可以被断言为其中一个类型 父类可以被断言为子类 任何类型都可以被断言为 any any 可以被断言为任何类型 要使得 A 能够被断言为 B,只需要 A 兼容 B 或 B 兼容 A 即可 类型声明是比类型断言更加严格 非空类型断言:message!.length

声明文件

全局变量

全局变量的声明文件主要有以下几种语法:

ts
declare var 声明全局变量
declare function 声明全局方法
declare class 声明全局类
declare enum 声明全局枚举类型
declare namespace 声明含有子属性的全局对象
interface type 声明全局类型注意interface 前是不需要 declare

导从引入(ES6规范)

ts
export default function foo(): string;
ts
import foo from 'foo';

foo();

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

导入引入(commonjs 规范)

ts
// 整体导出
module.exports = foo;
// 单个导出
exports.bar = bar;
ts
// 整体导入
const foo = require('foo');
// 单个导入
const bar = require('foo').bar;

第二种方式是 import ... from,注意针对整体导出,需要使用 import * as 来导入:

ts
// 整体导入
import * as foo from 'foo';
// 单个导入
import { bar } from 'foo';

第三种方式是 import ... require,这也是 ts 官方推荐的方式:

ts
// 整体导入
import foo = require('foo');
// 单个导入
import bar = require('foo').bar;

进阶

类型别名

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

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 = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames) {
    // do something
}

handleEvent(document.getElementById('hello'), 'scroll');  // 没问题
handleEvent(document.getElementById('world'), 'dblclick'); // 报错,event 不能为 'dblclick'

类型别名与字符串字面量类型都是使用 type 进行定义

元组

数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。

ts
let tom: [string, number] = ['Tom', 25];

枚举

枚举(Enum)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等。 枚举成员会被赋值为从 0 开始递增的数字,同时也会对枚举值到枚举名进行反向映射

ts
const enum Directions {
    Up,
    Down,
    Left,
    Right
}

let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

常数项和计算所得项

枚举项有两种类型:常数项(constant member)和计算所得项(computed member)。

前面我们所举的例子都是常数项,一个典型的计算所得项的例子:

ts
enum Color {Red, Green, Blue = "blue".length};

上面的例子中,"blue".length 就是一个计算所得项。

TypeScript 中类的用法

TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 public、private 和 protected(需要注意的是,TypeScript 编译之后的代码中,并没有限制 private 属性在外部的可访问性)

类与接口

之前学习过,接口(Interfaces)可以用于对「对象的形状(Shape)」进行描述。

这一章主要介绍接口的另一个用途,对类的一部分行为进行抽象。 实现(implements)是面向对象中的一个重要概念。一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用 implements 关键字来实现。这个特性大大提高了面向对象的灵活性。

ts
interface Alarm {
    alert(): void;
}

interface Light {
    lightOn(): void;
    lightOff(): void;
}

class Car implements Alarm, Light {
    alert() {
        console.log('Car alert');
    }
    lightOn() {
        console.log('Car light on');
    }
    lightOff() {
        console.log('Car light off');
    }
}

常见的面向对象语言中,接口是不能继承类的,但是在 TypeScript 中却是可以的

泛型

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。 <T> = 声明:定义一个新类型变量

简单的例子

ts
function createArray<T>(length: number, value: 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>,其中 T 用来指代任意输入的类型

多个类型参数

定义泛型的时候,可以一次定义多个类型参数:

ts
function swap<T, U>(tuple: [T, U]): [U, T] {
    return [tuple[1], tuple[0]];
}

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

泛型约束

ts
interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): 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;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

泛型参数的默认类型

在 TypeScript 2.3 以后,我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。

ts
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;
}

装饰器

ts
class Animal {
  type: string
  constructor(type: string) {
	  this.type = type
  }

  @yelling
  greet() {
    console.log(`Hello, I'm a(n) ${this.type}!`)
  }
}

const typeToYellingMap = {
  cat: 'meow~ meow~'
}

function yelling(originalMethod: any, context: ClassMethodDecoratorContext) {
  return function(...args: any[]) {
    console.log(typeToYellingMap[this.type])
    originalMethod.call(this, ...args)
  }
}

const xcat = new Animal('cat')
xcat.greet() // meow~ meow~
             // Hello, I'm a(n) cat!

上述示例对装饰器的应用属于方法装饰器,此类装饰器本身接收两个参数,一是被装饰的方法,二是方法装饰器的上下文。方法装饰器应返回一个函数,此函数在运行时真正被执行。在上述例子中,我们在装饰器返回的函数中做了两件事情:

泛型工具类

在深入讲解前,先奉上一张速查表,让你对所有工具类型有个整体印象。

分类工具类型作用
基础修饰Partial<T>将所有属性变为可选
Required<T>将所有属性变为必填
Readonly<T>将所有属性变为只读
结构挑选Pick<T, K>从 T 中挑选指定属性 K
Omit<T, K>从 T 中剔除指定属性 K
Record<K, T>构建一个键为 K、值为 T 的对象类型
类型过滤Exclude<T, U>从联合类型 T 排除类型 U
Extract<T, U>从联合类型 T 提取 U 类型
NonNullable<T>从 T 中排除 null 和 undefined
函数相关ReturnType<T>提取函数 T 的返回值类型
Parameters<T>提取函数 T 的参数类型元组
ConstructorParameters<T>提取构造函数 T 的参数类型元组
InstanceType<T>提取构造函数 T 的实例类型
ThisParameterType<T>提取函数 T 的 this 参数类型
OmitThisParameter<T>移除函数 T 的 this 参数限制

1 Record:权限表

ts
type Role = 'admin' | 'editor' | 'viewer';
type Permission = 'read' | 'write' | 'delete';

const rolePermissions: Record<Role, Permission[]> = {
  admin: ['read', 'write', 'delete'],
  editor: ['read', 'write'],
  viewer: ['read']
};

原理:Record<K, T> 本质就是“用 K 的所有键生成一组属性”:

ts
type Record<K extends keyof any, T> = {
  [P in K]: T;
}

2 Partial:更新用户信息

ts
interface UserProfile {
  id: number;
  name: string;
  email: string;
}
function updateUser(id: number, updates: Partial<UserProfile>) {
  // ...
}

updateUser(1, { email: 'new@example.com' });

原理:通过映射类型给所有属性加上 ?:

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

3 Omit:去除敏感字段

ts
interface User {
  id: number;
  name: string;
  password: string
}
type SafeUser = Omit<User, 'password'>;

原理:其实就是 Pick + Exclude 的组合:

ts
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>

4 Pick:只要部分字段

ts
type UserNameEmail = Pick<UserProfile, 'name' | 'email'>;

原理:只保留 K 中的键:

ts
type Pick<T, K extends keyof any> = {
  [P in K]: T[P]
}

5 Required:接口必填化

ts
type StrictUser = Required<Partial<UserProfile>>;

6 Readonly:让属性只读

ts
const config: Readonly<{ apiUrl: string }> = {
  apiUrl: 'https://api.example.com'
};
config.apiUrl = '' // 报错

7 Exclude & Extract:联合类型过滤

ts
type T = 'a' | 'b' | 'c';

type WithoutB = Exclude<T, 'b'>; // 'a' | 'c'
type OnlyB = Extract<T, 'b'>; // 'b'

对比:

Pick/Omit 好比一个 对象裁剪器:我有一张“用户表单”,我要么只留下 name/email(Pick),要么去掉 password(Omit)。 Exclude/Extract 好比一个 集合过滤器:我有一个集合 {'a','b','c'},我去掉 b(Exclude)或只要 b(Extract)。

3.8 NonNullable:去掉 null/undefined

ts
type Value = string | null | undefined;
type SafeValue = NonNullable<Value>; // string

3.9 ReturnType & InstanceType:类型推导

ts
function getUser() {
  return { id: 1, name: 'Alice' };
}
type UserReturn = ReturnType<typeof getUser>; 
// { id: number; name: string; }

class Person { name = 'Bob'; }
type PersonInstance = InstanceType<typeof Person>; 
// Person