Fork me on GitHub

TypeScript进阶

进阶
参考:https://ts.xcatliu.com/

类型别名

给一个类型起一个新名字

1
2
3
4
5
6
7
8
9
10
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 创建类型别名
类型别名常用于联合类型

交叉类型

1
type TouchEvent=React.TouchEvent&React.MouseEvent

将多种类型合并为一种类型,新的类型能访问到多个类型的所有属性

联合类型

1
2
type Source=10|20|30;
let a:Source=10;

a 的值只能是 10,20,30

1
2
3
4
5
6
const attr:number|string=20;
if(typeof attr === 'number'){
//attr可以使用number的所有方法
}else{
//attr可以使用string的所有方法
}

将多种类型合并为一种类型,新的类型能访问到多个类型的共有属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Bird{
fly()
layEggs()
}
interface Fish{
swim()
layEggs()
}
function getSmallPet():Fish|Bird{

}
let pet=getSmallPet()
pet.layEggs();
pet.fly();//报错

联合类型的类型保护
自定义类型保护

1
2
3
4
5
6
7
8
9
10
let pet=getSmallPet()
pet.layEggs();
if(isFish(pet)){
pet.swim()
}else{
pet.fly()
}
function isFish(pet:Fish|Bird):pet is Fish{
return (pet as Fish).swim!==undefined
}

typeof 类型保护

1
2
3
function isNumber(x: any): x is number {
return typeof x === "string";
}

instanceof 类型保护

1
2
3
function isFish(pet:Fish|Bird):pet is Fish{
return pet instanceof Fish
}

不为 null 的类型断言!

1
name!.indexOf('a')

自定义属性类型保护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Circle {
kind: "circle";
r: number;
}
type Shape = Square | Rectangle | Circle;
function area(s: Shape) {
switch (s.kind) {
case "square":
return s.size * s.size;
case "rectangle":
return s.width * s.height;
case "circle":
return Math.PI * s.r ** 2;
default:
return ((e: never) => {
throw new Error(e);
})(s); //判断s是否为never类型,如果是,说明s不存在Shape中,否则说明上面的case有遗漏
}
}
console.log(area({ kind: "circle", r: 10 }));

索引类型

keyof T,返回 T 中所有属性的联合类型
T[k],返回 T 中 K 元素的类型
T extends U,T 继承至 U

1
2
3
4
5
6
7
8
9
10
let obj = {
a: 1,
b: '2',
c: 3
}

function getValues<T, K extends keyof T>(obj: T, keys: K[]):T[K][] {
return keys.map(key=>obj[key])
}
console.log(getValues(obj, ['a', 'b']))

映射类型

1
2
3
4
5
6
7
8
9
interface Obj{
a: string,
b: string,
c: number
}
type ReadonlyObj = Readonly<Obj>//只读
type PartialObj = Partial<Obj>//可选
type PickObj=Pick<Obj,'a'|'c'>//抽取部分属性
type RecordObj = Record<'x' | 'y', Obj>//创建新类型,参数类型为Obj

条件类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type TypeName < T >=
T extends string ? 'string' :
T extends number ? 'number' :
T extends boolean ? 'boolean' :
T extends undefined ? 'undefined' :
T extends Function ? 'function' :
"object"

type T1 = TypeName<string>
type T2 = TypeName<string[]>
//(A|B) extends U?x:y
//A extends U ? x : y | B extends U ? x : y
type T3 = TypeName<string | string[]>

type Diff<T, U> = T extends U ? never : T
type T4 = Diff<'a' | 'b' | 'c', 'a' | 'e'>
//Diff<'a','a'|'e'> | Diff<'b','a'|'e'>| Diff<'c','a'|'e'>
// 'never'|'b'|'c'
//'b'|'c'

type NotNull<T> = Diff<T, undefined | null>
type TS = NotNull<string | number | undefined | null>

内置条件类型

1
2
3
4
Exclude<T,U>//同上diff
NotNullable<T>//同上NotNull
Extract<T,U>//与Exclude相反,返回T中继承至U的类型
ReturnType<fun>//返回函数返回值类型

字符串字面量类型

用来约束取值只能是某几个字符串中的一个

1
2
3
4
type EventNames = "click" | "scroll" | "mousemove";
function handleEvent(ele: Element, event: EventNames) {}
handleEvent(document.getElementById("hello"), "scroll");
handleEvent(document.getElementById("world"), "dbclick");

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

元组

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

声明

1
let jerry: [string, number] = ["jerry", 25];

赋值

1
2
let jerry: [string, number];
jerry=['jerry',22]

直接对元组赋值,需要提供所有元组类型中指定的项。

访问

1
2
jerry[0].slice(1);
jerry[1].slice(2);//报错

越界元素

当添加越界的元素时,它的类型被限制为元组中每个类型的联合类型

1
2
3
4
let jerry: [string, number];
jerry = ["jerry", 22];
jerry.push("female");//成功
jerry.push(true);// error TS2345: Argument of type 'true' is not assignable to parameter of type 'string | number'.

枚举

Enum
定义

1
2
3
4
5
6
7
8
9
enum Days {
sun,
Mon,
Tue,
Web,
Thu,
Fri,
Sat
}

枚举成员会被赋值为从 0 开始递增的数字,同时也会对枚举值到枚举名进行反向映射。
编译结果

1
2
3
4
5
6
7
8
9
10
var Days;
(function (Days) {
Days[Days["Sun"] = 0] = "Sun";
Days[Days["Mon"] = 1] = "Mon";
Days[Days["Tue"] = 2] = "Tue";
Days[Days["Web"] = 3] = "Web";
Days[Days["Thu"] = 4] = "Thu";
Days[Days["Fri"] = 5] = "Fri";
Days[Days["Sat"] = 6] = "Sat";
})(Days || (Days = {}));

映射

1
2
3
4
5
6
7
8
console.log(Days["Sun"] === 0); //true
console.log(Days["Mon"] === 1); //true
console.log(Days["Tue"] === 2); //true
console.log(Days["Web"] === 3); //true

console.log(Days[4] === "Thu"); //true
console.log(Days[5] === "Fri"); //false
console.log(Days[6] === "Sat"); //true

手动赋值枚举项,可以不是数字,但是需要使用让 tsc 无视类型检查,但是后面的枚举项也需要手动赋值,否则无法获取初始值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum Days {
Sun = 7,
Mon,
Tue,
Web,
Thu,
Fri,
Sat = <any>"s"
}
console.log(Days["Sun"] === 7); //true
console.log(Days["Mon"] === 8); //true

console.log(Days[11] === "Thu"); //true
console.log(Days[12] === "Fri"); //true
console.log(Days["s"] === "Sat"); //true

编译后

1
2
3
4
5
6
7
8
9
10
var Days;
(function (Days) {
Days[Days["Sun"] = 7] = "Sun";
Days[Days["Mon"] = 8] = "Mon";
Days[Days["Tue"] = 9] = "Tue";
Days[Days["Web"] = 10] = "Web";
Days[Days["Thu"] = 11] = "Thu";
Days[Days["Fri"] = 12] = "Fri";
Days[Days["Sat"] = "s"] = "Sat";
})(Days || (Days = {}));

手动赋值的枚举项也可以是小数或负数,后面项的递增步长是 1

1
2
3
4
5
6
7
8
9
10
11
12
13
enum Days {
Sun = 7,
Mon = 1.5,
Tue,
Web,
Thu,
Fri,
Sat = <any>"s"
}
console.log(Days["Sun"] === 7); //true
console.log(Days["Mon"] === 1.5); //true
console.log(Days["Tue"] === 2.5); //true
console.log(Days["Web"] === 3.5); //true

常数项和计算所得项

前面的例子都是常数项
计算所得项例子:

1
2
3
4
5
enum Color {
Red,
Green,
Blue = "blue".length
}

上面的例子不会报错,但是如果后面还有未手动赋值的项,就会因无法获得初始值而报错。

1
2
3
4
5
6
7
8
9
10
11
enum Color {
Red = "red".length,
Green,
Blue
}
//error TS1061: Enum member must have initializer.

//236 Green,
//error TS1061: Enum member must have initializer.

//237 Blue

当满足以下条件时,枚举成员被当作常数:

  • 不具有初始化函数并且之前的枚举成员是常数,后面的枚举成员就是前面的值+1,第一个枚举成员默认初始值为 0;
  • 枚举成员使用常数枚举表达式初始化。常数枚举表达式是 TypeScript 表达式的子集,可以在编译阶段求值,当一个表达式满足下面条件之一时,就是一个常数枚举表达式:
    • 数字字面量
    • 引用之前定义的常数枚举成员
    • 带括号的常数枚举表达式,如果这个成员是在同一个枚举类型中定义的,可以使用非限定名来引用
    • +-~一元运算符应用于常数枚举表达式
    • +-*/%<<>>>>>&|^二元运算符,常数枚举表达式作为其一个操作对象。若常数枚举表达式求值后为NaNInfinity,则会在编译阶段报错。

所有其它情况的枚举成员被当作是需要计算得出的值。

常数枚举

const enum定义的枚举类型

1
2
3
4
5
6
7
8
9
10
11
12
const enum Directions {
Up,
Down,
Left,
Right
}
let directions = [
Directions.Up,
Directions.Down,
Directions.Left,
Directions.Right
];

编译为:

1
2
3
4
5
6
var directions = [
0,
1,
2,
3
];

常数枚举与普通枚举的区别在编译阶段被删除,并且不能包含计算成员
假如包含了计算成员,则会在编译阶段报错

1
2
3
4
5
6
const enum Colors {
Red,
Green,
Blue = "blue".length
}
//error TS2474: const enum member initializers can only contain literal values and other computed enum values.

外部枚举

使用declare声明枚举类型

1
2
3
4
5
6
7
8
9
10
11
12
declare enum Directions {
Up,
Down,
Left,
Right
}
let directions = [
Directions.Up,
Directions.Down,
Directions.Left,
Directions.Right
];

编译后

1
2
3
4
5
6
var directions = [
Directions.Up,
Directions.Down,
Directions.Left,
Directions.Right
];

外部枚举与声明语句一样,常出现在声明文件中。
同时使用declareconst

1
2
3
4
5
6
7
8
9
10
11
12
declare const enum Directions {
Up,
Down,
Left,
Right
}
let directions = [
Directions.Up,
Directions.Down,
Directions.Left,
Directions.Right
];

编译为

1
2
3
4
5
6
var directions = [
0,
1,
2,
3
];

类的概念

  • 类(Class):定义一件事物的抽象特定,包含属性和方法
  • 对象(Object):类的实例,通过 new 生成
  • 面向对象(OOP)的三大特点:封装、继承、多态
  • 封装(Encapsulation):将对数据的操作细节隐藏起来,只暴露对外的接口。调用端不需要知道细节,只能通过对外接口来访问该对象,同时也保证外界无法任意更改对象内部的数据
  • 继承(Inheritance):子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性
  • 多态(Polymorphism):由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。
  • 存取器(getter & setter):用以改变属性的读取和赋值行为
  • 修饰器(Modifiers):修饰符是关键字,用以限定成员或类型的性质。比如 public 表示公有属性或方法
  • 抽象类(Abstract Class):抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现
  • 接口(Interfaces):不同类之间公有的属性或方法,可以抽象成一个接口。

ES6 中类的用法

属性和方法

1
2
3
4
5
6
7
8
9
10
class Animal {
constructor(name) {
this.name = name;
}
sayHi() {
return `My name is ${this.name}`;
}
}
let a = new Animal("Jack");
console.log(a.sayHi()); //My name is Jack

使用class定义类,使用constructor定义构造函数。
使用new实例化

类的继承

1
2
3
4
5
6
7
8
9
10
11
class Cat extends Animal {
constructor(name) {
super(name); //调用父类的constructor(name)
console.log(this.name);
}
sayHi() {
return "Meow," + super.sayHi(); //调用父类的sayHi()
}
}
let c = new Cat("Tom"); //Tom
console.log(c.sayHi());//Meow,My name is Tom

存取器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

使用 getter 和 setter 改变属性的赋值和读取行为

静态方法

使用static修饰符修饰的方法称为静态方法,不需要实例化,而是直接通过类来调用:

1
2
3
4
5
6
7
8
class Animal {
static isAnimal(a) {
return a instanceof Animal;
}
}
let a = new Animal("Kitty"); //setter:Kitty
console.log(Animal.isAnimal(a)); //true
a.isAnimal(a);//a.isAnimal is not a function

ES7 中类的用法

实例属性

ES6 中实例的属性只能通过构造函数中的this.xxx来定义,ES7 提案中可以在类中定义

1
2
3
4
5
6
class Animal {
name = "Jack";
constructor() {}
}
let a = new Animal();
console.log(a.name);//Jack

静态属性

ES7 提案中,可以使用static定义一个静态属性

1
2
3
4
5
6
7
8
9
class Animal {
static num = 42;

constructor() {
// ...
}
}

console.log(Animal.num); // 42

TypeScript 中类的用法

访问修饰符

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

public

1
2
3
4
5
6
7
8
9
10
class Animal {
public name;
public constructor(name) {
this.name = name;
}
}
let an = new Animal("Jack");
console.log(an.name); //Jack
an.name = "Tom";
console.log(an.name); //Tom

private

希望有的属性无法直接存取的

1
2
3
4
5
6
7
8
9
10
11
class Animal {
private name;
public constructor(name) {
this.name = name;
}
}
let an = new Animal("Jack");
console.log(an.name);
//error TS2341: Property 'name' is private and only accessible within class 'Animal'.
an.name = "Tom";
//error TS2341: Property 'name' is private and only accessible within class 'Animal'.

编译后

1
2
3
4
5
6
7
8
9
var Animal = (function () {
function Animal(name) {
this.name = name;
}
return Animal;
}());
var an = new Animal("Jack");
console.log(an.name);
an.name = "Tom";

注意,TypeScript 编译之后的代码中,并没有限制 private 属性在外部的可访问性。

private修饰的属性或方法,在子类中也是不允许访问的

1
2
3
4
5
6
7
8
9
10
11
12
13
class Animal {
private name;
public constructor(name) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name) {
super(name);
console.log(this.name);
}
}
//error TS2341: Property 'name' is private and only accessible within class 'Animal'.

当构造函数修饰为private时,该类不允许被继承或实例化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Animal {
public name;
private constructor(name) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name) {
super(name);
}
}
let a = new Animal("Jack");
//error TS2451: Cannot redeclare block-scoped variable 'a'
//error TS2673: Constructor of class 'Animal' is private and only accessible within the class declaration.

修饰符还可以使用在构造函数参数中,等同于类中定义该属性,使代码更简洁

1
2
3
4
5
6
class Animal {
// public name: string;
public constructor(public name) {
this.name = name;
}
}

protected

如果使用protected修饰属性,则允许在子类中访问

1
2
3
4
5
6
7
8
9
10
11
12
class Animal {
protected name;
public constructor(name) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name) {
super(name);
console.log(this.name);
}
}

protected修饰构造函数时,该类只允许被继承

1
2
3
4
5
6
7
8
9
10
11
12
13
class Animal {
public name;
protected constructor(name) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name) {
super(name);
}
}
let a = new Animal("Jack");
//error TS2674: Constructor of class 'Animal' is protected and only accessible within the class declaration.

readonly

只读关键字属性,只允许出现在属性声明或索引签名中。

1
2
3
4
5
6
7
8
9
10
class Animal {
readonly name;
public constructor(name) {
this.name = name;
}
}
let an = new Animal("Jack");
console.log(an.name); //Jack
a.name = "Tom";
//error TS2540: Cannot assign to 'name' because it is a read-only property.

注意,如果 readonly 和其他访问修饰符同时存在的话,需要写在其后面

1
2
3
4
5
class Animal {
public constructor(public readonly name) {
this.name = name;
}
}

抽象类

abstract用于定义抽象类和其中的抽象方法

  1. 抽象类不允许被实例化
1
2
3
4
5
6
7
8
9
abstract class Animal {
public name;
public constructor(name) {
this.name = name;
}
public abstract sayHi();
}
let a = new Animal("Jack");
// error TS2511: Cannot create an instance of an abstract class.
  1. 抽象类中的抽象方法必须被子类实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
abstract class Animal {
public name;
public constructor(name) {
this.name = name;
}
public abstract sayHi();
}
class Cat extends Animal {
public eat() {
console.log(`${this.name}is eating.`);
}
}
let cat = new Cat("Tom");
//error TS2515: Non-abstract class 'Cat' does not implement inherited abstract member 'sayHi' from class 'Animal'.

继承抽象类,实现抽象方法

1
2
3
4
5
6
7
8
9
10
11
12
13
abstract class Animal {
public name;
public constructor(name) {
this.name = name;
}
public abstract sayHi();
}
class Cat extends Animal {
public sayHi() {
console.log(`Meow,My name is ${this.name}.`);
}
}
let cat = new Cat("Tom");

编译结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var Animal = (function () {
function Animal(name) {
this.name = name;
}
return Animal;
}());
var Cat = (function (_super) {
__extends(Cat, _super);
function Cat() {
return _super !== null && _super.apply(this, arguments) || this;
}
Cat.prototype.sayHi = function () {
console.log("Meow\uFF0CMy name is " + this.name + ".");
};
return Cat;
}(Animal));
var cat = new Cat("Tom");

需要注意的是,即使是抽象方法,TypeScript 的编译结果中,仍然会存在这个类

类的类型

给类加上 TypeScript 的类型

1
2
3
4
5
6
7
8
9
10
11
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
sayHi(): string {
return `My name is ${this.name}.`;
}
}
let an: Animal = new Animal("Jack");
console.log(an.sayHi());//My name is Jack.

在 TypeScript 使用泛型创建工厂函数时,需要引用构造函数的类类型

1
2
3
function create<T>(c: {new(): T; }): T {//这边的new()不好理解
return new c();
}

传入一个参数,此参数实例化后生成是 T 类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class BeeKeeper {
hasMask: boolean;
}

class ZooKeeper {
nametag: string;
}

class Animal {
numLegs: number;
}

class Bee extends Animal {
keeper: BeeKeeper;
}

class Lion extends Animal {
keeper: ZooKeeper;
}

function createInstance<A extends Animal>(c: new () => A): A {
return new c();
}

createInstance(Lion).keeper.nametag; // typechecks!
createInstance(Bee).keeper.hasMask; // typechecks!

createInstance 函数的参数是构造函数没有参数的 A 类的类型。

类与接口

类实现接口

不同类之间共有特性,把特性提取成接口,用implements关键字实现。提高了面向对象的灵活性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Alarm {
alert();
}
class Door {}
class SecurityDoor extends Door implements Alarm {
alert() {
console.log("SecurityDoor alert");
}
}
class Car implements Alarm {
alert() {
console.log("Car alert");
}
}

一个类实现多个接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface Alarm {
alert();
}
interface Light {
lightOn();
lightOff();
}
class Door {}
class Car implements Alarm {
alert() {
console.log("Car alert");
}
lightOn() {
console.log("Car light on");
}
lightOff() {
console.log("Car light off");
}
}

接口继承接口

1
2
3
4
5
6
7
8
9
10
11
interface Alarm {
alert();
}
interface Light {
lightOn();
lightOff();
}
interface LightableAlarm extends Alarm {
lightOn();
lightOff();
}

接口继承类

1
2
3
4
5
6
7
8
class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: number;
}
let Point3d: Point3d = { x: 1, y: 2, z: 3 };

混合类型

使用接口定义一个函数需要符合的形状

1
2
3
4
5
6
7
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
return source.search(subString) !== 1;
};

函数还可以有自己的属性和方法

1
2
3
4
5
6
7
8
9
10
function getCounter(): Counter {
let counter = <Counter>function(start: number) {};
counter.interval = 123;
counter.reset = function() {};
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;

泛型

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

例子-简单

创建一个指定长度的数组,同时将每一项填充默认值

1
2
3
4
5
6
7
8
function createArray(length: number, value: any): Array<any> {
let result = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result
}
createArray(3, 'x'); // ['x', 'x', 'x']

返回值为数组泛型,并没有准确定义。实际上每一项的类型就是value的类型

1
2
3
4
5
6
7
8
function createArray<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
console.log(createArray<string>(3, "x"));

函数名字后的 T 定义任意输入类型,后面的参数value:T和输出Array<T>中即可使用。
调用的时候可以在函数名称后面指定类型,也可以不指定,而让类型推论自动推算。

多个类型参数

一次定义多个泛型参数

1
2
3
4
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap([7, "seven"]);

交换元组

泛型约束

在函数内部使用泛型变量,由于不知道它是哪种类型,所以不能随意操作它的属性或方法。

1
2
3
4
5
function loggingIdentity<T>(arg: T): T {
console.log(arg.length);
return arg;
}
// error TS2339: Property 'length' does not exist on type 'T'.

可以对泛型进行约束,只允许传入那些包含length属性的变量:

1
2
3
4
5
6
7
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}

使用extends约束泛型T,必须符合接口Lengthwise的形状,也就是必须包含length属性
如果传入的参数不符合类型约束,编译阶段就会报错

1
2
loggingIdentity(7);
//error TS2345: Argument of type '7' is not assignable to parameter of type 'Lengthwise'.

多个类型参数之间也能相互约束:

1
2
3
4
5
6
7
8
function copyFields<T extends U, U>(target: T, source: U): T {
for (let id in source) {
target[id] = (<T>source)[id];
}
return target;
}
let t = { a: 1, b: 2, c: 3, d: 4 };
copyFields(t, { b: 10, d: 20 });

使用了两个类型参数,其中要求 T 继承 U,这样就保证了 U 上不会出现 T 中不存在的字段。

泛型接口

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface CreateArrayFunc {
<T>(length: number, value: T): Array<T>;
}

let createArray: CreateArrayFunc;
createArray = function<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
};

console.log(createArray(3, "x")); //['x','x','x']

可以把泛型接口提前到接口名上:

1
2
3
4
5
6
7
8
9
10
11
12
13
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']

注意,此时在使用泛型接口的时候,需要定义泛型的类型。

泛型类

1
2
3
4
5
6
7
8
9
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;
};

泛型参数的默认类型

TypeScript2.3 以后,可以为泛型中的类型参数指定默认类 U 型,当使用泛型时没有在代码中直接指定类型参数,从实际参数中也无法推测出时,默认类型就会起作用。

1
2
3
4
5
6
7
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;
}

声明合并

如果定义了两个相同名字的函数、接口或类,会合并成一个类型。

接口的声明合并

1.非函数成员
注意,合并属性的类型必须是唯一的

1
2
3
4
5
6
7
8
9
10
11
12
13
interface A{
x: number;
y: number,
}
interface A{
y: number;

}
let a: A = {
x: 1,
y: 1,

}

2.函数成员
函数重载顺序:接口内部安装书写顺序,正序(先写的在前),接口之间,先写的在后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface A{
x: number;
foo(bar: number): number;//3
}
interface A{
y: number;
foo(bar: string): string;//1
foo(bar: number[]):number[]//2
}
let a: A = {
x: 1,
y: 1,
foo(bar: any) {
return bar
}
}

如果函数参数是字符串字面量,该函数声明会提升到最前面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
interface A{
x: number;
foo(bar: number): number;//5
foo(bar: 'a'): number//2

}
interface A{
y: number;
foo(bar: string): string;//3
foo(bar: number[]): number[];//4
foo(bar: 'b'): number;//1

}
let a: A = {
x: 1,
y: 1,
foo(bar:any):any {
return bar
}
}

命名空间和其他类型的合并

就相当于在原数据上增加属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Lib() { }
namespace Lib{
export let version='1.0'
}
console.log(Lib.version)//1.0

class C { }
namespace C{
export let state=1
}
console.log(C.state)//1

enum Color{
Green,
Red,
Yellow
}
namespace Color{
export function mix() {
return 1
}
}
console.log(Color.mix())//1

函数的合并

重载定义多个函数类型

1
2
3
4
5
6
7
8
9
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('');
}
}

类的合并

与接口的合并规则一致

模块导入与导出

ES6 模块导出,commonJS 模块导入

1
2
3
4
5
6
7
8
9
10
11
12
13

//导出 es6/d.ts
export =function(){
console.log("I'm default")
}
//不能有其他导出


//导入 node/c.ts
import fun =require('../es6/d')
//如果项目tsconfig配置“esModuleInterop”:true
//可以写成
import fun from '../ed6/d'

编写声明文件

global 库

//global-lib.js

1
2
3
4
5
6
7
function globalLib(options) {
console.log(options);
}
globalLib.version = "1.0.0";
globalLib.doSomething = function () {
console.log("globalLib do somethings");
};

global-lib.d.ts

1
2
3
4
5
6
7
8
declare function globalLib(options: globalLib.Options): void;
declare namespace globalLib {
const version: string;
function doSomething(): void;
interface Options {
[key: string]: any;
}
}

使用
引入<script src='src/lib/global-lib.js'></script>

1
2
globalLib({ x: 1 });
globalLib.doSomething();

module 库

module-lib.js

1
2
3
4
5
6
7
8
9
10
const version = "1.0.0";
function doSomething() {
console.log("moduleLib do something");
}
function moduleLib(options) {
console.log(options);
}
moduleLib.version = version;
moduleLib.doSomething = doSomething;
module.exports = moduleLib;

module-lib.d.ts

1
2
3
4
5
6
7
8
9
declare function moduleLib(options: Options): void;
interface Options {
[key: string]: any;
}
declare namespace moduleLib {
export const version: string;
function doSomething(): void;
}
export = moduleLib;

使用

1
2
3
import moduleLib from "./module-lib";
moduleLib({ x: 2 });
moduleLib.doSomething();

umd 库

umd-lib.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(function (root, factory) {
if (typeof define === "function" && define.amd) {
define(factory);
} else if (typeof module === "object" && module.exports) {
module.exports = factory();
} else {
root.umdLib = factory();
}
})(this, function () {
return {
version: "1.0.0",
doSomething() {
console.log("umdLib do something");
},
};
});

umd-lib.d.ts

1
2
3
4
5
6
declare namespace umdLib {
const version: string;
function doSomething(): void;
}
export as namespace umdLib;
export = umdLib;

使用 1

1
2
import umdLib from "./umd-lib.js";
umdLib.doSomething();

使用 2
引入<script src='src/lib/umd-lib.js'></script>
需要 ts 配置"allowUmdGlobalAccess": true

1
umdLib.doSomething();

为库增加属性

1
2
3
4
5
import m from "moment";
declare module "moment" {
export function myFunction(): void;
}
m.myFunction = () => {};

为全局类库增加方法(不建议使用)

1
2
3
4
5
6
declare global {
namespace globalLib {
function doAnything(): void;
}
}
globalLib.doAnything = () => {};

ts 配置选项

默认配置:ts -- init生成 ts 默认配置文件

文件选项

files :指定 ts 编译的文件,数组

"files": ["src/a.ts"]

include :指定 ts 编译的路径或文件

"include": ["src/a.ts","src/lib"]
支持通配符
"src":包括 src 里面所有的文件
"src/*":仅包括 src 一级目录的文件
"src/*/*":仅包括 src 二级目录的文件

编译配置拆分
tsconfig.base.json

1
2
3
4
5
{
"files": ["src/a.ts"],
"include": ["src"], //通配符 src/*仅src一级目录,src/*/*仅src二级目录
"exclude": ["src/lib"]
}

tsconfig.json

1
2
3
4
{
"extends": "./tsconfig.base.json",
"exclude": []//覆盖base里面属性
}

编译选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
{
"include": ["src"],
"compilerOptions": {
/* Basic Options */
// "incremental": true /* 增量编译,生成编译文件 */,
// "diagnostics": true /*打印诊断信息*/,
// "tsBuildInfoFile": "./buildFile/" /* 指定增量编译文件的名字 */,

"target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
// "module": "amd" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
// "outFile": "./app.js" /* 多个相互依赖的文件编译成一个文件,用于AMD模块中 "module": "amd"*/,

// "lib": [
// "dom",
// "es5",
// "ScriptHost",
// "ES2019.Array"
// ] /*TS需要引用的库,即声明文件,。es5默认"dom","es5","ScriptHost" */,

// "allowJs": true /* 允许编译js文件 (.js,jsx) 要指定输出目录,否则报错覆盖原文件*/,
// "checkJs": true /* 允许报告.js文件中的错误,与allowJs配合使用 */,
// "outDir": "./dest" /* 指定输出目录 */,
// "rootDir": "./src" /* 指定输入文件目录(用于控制输出目录结构) ./:输出dest包含src目录,./src:输出dest不包含src目录*/,

// "declaration": true /* 生成声明文件 '.d.ts' */,
// "declarationDir": "./types" /* 声明文件的目录 相对于根目录*/,
// "emitDeclarationOnly": true /*只生成声明文件*/,

// "sourceMap": true /* 生成source map文件*/,

// "inlineSourceMap": true /*行内source map文件*/,
// "declarationMap": true /* 声明文件的source map文件 */,

// "typeRoots": [] /* 声明文件目录。默认node_modules/@types */,
// "types": [] /* 指定需要加载的声明文件包*/

// "removeComments": true /* 删除注释 */,

// "noEmit": true /* 不输出任何文件 */,
// "noEmitOnError": true /* 出错时不输出任何文件 */,

// "noEmitHelpers": true /* 不生成helper函数, 安装ts-helpers*/,
// "importHelpers": true /* 通过tslib引入helper函数,文件必须是模块 */,

// "downlevelIteration": true /*降级遍历器实现(es3/es5)*/,

/* Strict Type-Checking Options */
"strict": true /* 开启所有严格的类型检查,为true时,下面所有配置都为true */,
// "noImplicitAny": true, /* 不允许隐式的any类型 */
// "strictNullChecks": true, /* 不允许把null、undefined赋值给其他类型的变量 */
// "strictFunctionTypes": true, /* 不允许函数参数双向协变*/
// "strictBindCallApply": false /* 严格的'bind', 'call', and 'apply' 检查,参数类型不再默认转换*/,
// "strictPropertyInitialization": true, /* 类的实例属性必须初始化 */
// "noImplicitThis": false /* 不允许this隐式的'any' 类型. */,
// "alwaysStrict": true, /* 在代码中注入"use strict"*/

/* Additional Checks warning*/
// "noUnusedLocals": true, /*不允许出现声明但未使用的局部变量 */
// "noUnusedParameters": true, /* 检查未使用的函数参会时 */
// "noImplicitReturns": true, /* 每个分支都要有返回值*/
// "noFallthroughCasesInSwitch": true, /* 防止switch语句贯穿 */

// "esModuleInterop": true /*允许export=导出,由import from 导入 */,
// "allowUmdGlobalAccess": true, /* 允许在模块中访问UMD全局变量 */
// "moduleResolution": "node", /* 模块解析策略 */
// "baseUrl": "./", /* 解析非相对模块的基地址 */
// "paths": {
// "jquery":["node_modules/jquery/jquery/dist/jquery.min.js"]
// }, /* 路径映射,相对于 'baseUrl'. */
// "rootDirs": ["src","out"], /* 项目运行时将多个目录放到一个虚拟目录下 */
"jsx": "React" /* 'preserve'生成代码保留jsx格式,扩展名jsx,可以被babel使用, 'React-native'生成代码保留jsx格式,扩展名.js, or 'React'生成代码js语法 */,
"listEmittedFiles": true /* 打印编译后的输出文件路径*/,
"listFiles": true /*打印编译文件,(包括引用的声明文件)*/
}
}

babel 配置

1
2
3
4
loader: "ts-loader",
options: {
transpileOnly: true, //只做语言转换,不做类型检查
},

提高打包效率
可以另外安装插件类型检查

1
2
3
4
5
npm i fork-ts-checker-webpack-plugin -D

plugins: [
new ForkTsCheckerWebpackPlugin(),
],
-------------本文结束感谢阅读-------------