Typescript笔记

官方学习文档

Handbook - The TypeScript Handbook

基础类型:声明变量

布尔类型

布尔也就两种, true 或者 false

1
2
3
4
5
6
7
let isBoolean: boolean = false;

function tell() {
alert(isBoolean)
}

tell()

number

数值类型有很不止 numberbigint也是。同时值的话可以是十进制,二进制,还可以是NaN。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let num: number = 10;
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
let big: bigint = 100n;
let nan: number = NaN;

function tell() {
num--;
console.log(num)
}

tell()

string

字符串,可以是普通字符串,还可以是模板字符串

1
2
3
let fullName: string = `Bob Bobbington`;
let age: number = 37;
let sentence: string = `Hello, my name is ${fullName}.

array

1
2
3
let arr:number[] = [1, 2, 3];
// 等同于
let arr:Array<number> = [1, 2, 3];

Tuple 元组

元组看成是数组的升级版,可以定义数组里面的每一项的数据类型,在赋值。

1
2
3
4
let x: [string, number];
x = ['踏浪', 18]; // OK

x = [18, '踏浪']; // Error

要求每一项的数据类型都和定义的类型一致,否则就会报错

https://pic-go-1253455210.cos.ap-chengdu.myqcloud.com/blog/20201028113749.png

同时,也不能在 x 中添加第三项,因为没有定义

1
2
3
Tuple type '[string, number]' of length '2' has no element at index '3'.

7 x[3] = 100;

枚举 Enum

枚举类型将值限定在一定的范围内,比如一周只有7天,一年只有12个月

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum Color {Red, Green, Yellow, Blue};
enum Color2 {Red = 10, Green, Yellow = 30, Blue = 20};

let colorName: string = Color[1]; // 类似数组获取下标
let color2Name: string = Color2[11]; // 自定义下标顺序

function tell() {
console.log(colorName); // Green
console.log(color2Name); // Green
console.log(Color2[30]); // Yellow 没有固定顺序的定义
console.log(Color2[20]); // Blue 没有固定顺序的定义
}

tell();

Any 任意类型

1
2
3
4
5
6
7
8
9
let a: any = 10;

a = "踏浪";

a = true;

a = function() {};

a = [1, 2, 3]

定义 any 类型后,就有点像是普通的js了,可以任意修改值类型

void 函数返回值声明,函数不需要任何返回值

1
2
3
4
5
6
7
8
9
10
11
function tell(): number {
return 18;
}

function say(): string {
return '踏浪'
}

function other(): void {
return 100; //Error, Type 'number' is not assignable to type 'void'.
}

使用了 void 就不要有返回值了

Null 和 undefined

唯一的 nullundefined

1
2
let u: undefined = undefined;
let n: null = null;

Object 对象

Object是代表非基本类型的类型,即不是数字,字符串,布尔值,bigint,symbol,null或者undefined的任何类型。

1
2
3
4
5
declare function create(o: object): void;

create({name: '踏浪'});
create([1, 2, 3]);
create(10); // Argument of type 'number' is not assignable to parameter of type 'object'.

除了上述的这些类型,基本类型还有 Unknown Never 等,这些可以去上面提到的官网查看。

函数

在TS中函数也有具名函数与匿名函数之分,只是相对TS中,函数的参数以及返回值都可以指定参数类型

1
2
3
function add(x: number, y: number): number {
return x + y;
}

上面的代码参数与函数返回值的类型都是 number

如果是匿名函数

1
2
3
let sayName = function(x: string, y: number): string {
return `我是${x}, ${y}岁`;
}

上面的函数如果不看返回值,要想知道 xy 的意义是很难的,所以,针对于匿名函数,还有一种方式,可以指定参数的具体的意义

1
2
3
let sayName: (name: string, age: number) => string = function(x: string, y: number): string {
return `我是${x}, ${y}岁`;
}

前面的 (name: string, age: number) 指的就是函数的参数意义, x 表示 namey 表示 age ,后面的 =>string 则是指定函数的返回值为 string 类型的。

虽然

可选参数与默认参数

1
2
3
4
function sayName(firstName: string, lastName: string): string {
return `${firstName} ${lastName}`
}
sayName('踏', '浪');

上面的函数接收两个参数,在TS中如果多传递或者少传递参数编译都不会通过,必须传递两个。但是如果我只传递一个参数呢?TS中有一个可选参数,用 ?: 表示

1
2
3
4
5
6
7
function sayName(firstName: string, lastName?: string): string {
return lastName ? `${firstName} ${lastName}` : firstName
}

sayName('踏', '浪');

sayName('踏');

还可以设置默认值,有点像是JS中的默认值

1
2
3
4
5
6
function sayName(firstName: string, lastName = "浪"): string {
return lastName ? `${firstName} ${lastName}` : firstName
}

sayName('踏'); // 踏 浪
sayName('踏', '❄️'); // 踏 ❄️

剩余参数

与JS中的 ... 一样,只是可以指定类型

1
2
3
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}

类 classes

基本写法

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
say() {
return `${this.name}: ${this.age}`;
}
}

let person = new Person();

上面的代码会有错误 error TS2554: Expected 2 arguments, but got 0.

因为上面我们使用了 constructor ,所以在创建实例的时候必须传递两个参数才可以。

1
let person = new Person('踏浪', 18);

注意参数类型需要与你的类的 constructor 参数的类型相同。

类的继承

继承是用 extends 关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
say() {
return `${this.name}: ${this.age}`;
}
}

class Student extends Person {
school: string;
constructor(school: string) {
this.school = school;
}
tell() {
return `${this.say()} ${this.school}`
}
}

let person = new Person('踏浪', 18);

上面的 Student 类就是继承于父类 Person 的。但是如果你使用带有提示的编辑器,就会发现有错。错误如下:

https://pic-go-1253455210.cos.ap-chengdu.myqcloud.com/blog/20201030100409.png

错误中提示我们必须要有一个 super 。那么super接收什么参数呢。接收父类 constructor 需要的参数,这一点与ES6中的class继承一节的内容相似。可以看看

ES6 入门教程

如果子类不调用super方法,就无法得到this对象。

1
2
3
4
5
6
...
constructor(school: string) {
super('踏浪', 18);
this.school = school;
}
...

这样就行了。

super 如果是在子类的 constructor 中调用,那么相当于是调用父类的 constructor 方法。如果是在其他非constructor 的地方,就表示一个指向父级的对象,相当于 this 。比如上面代码中的 this.say() 就可以写成 super.say()

1
2
3
tell() {
return `${super.say()} ${this.school}`
}

访问修饰符

访问修饰符有两个 public(默认)private

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 Person {
public name: string;
private age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
private say() {
return `${this.name}: ${this.age}`;
}
}

class Student extends Person {
school: string;
constructor(school: string) {
super('踏浪', 18);
this.school = school;
}
tell() {
return `${super.say()} ${this.school}`
}
}

let person = new Person('踏浪', 18);
let student = new Student('北大');
console.log(student.tell());

上面的代码编译会出错。我们在父类的 name agesay 属性与方法前面添加了访问修饰符,子类调用 super.say() 方法出错 Property 'say' is private and only accessible within class 'Person'.

所以,属性或者方法前面添加private 后子类无法访问,不影响父类自己使用,如果父类 say 方法中的 this.age

通过 getter setter 访问,修改私有属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Hello {
private _name: string;
say() {
return `I am ${this._name}`;
}
get name() {
return this._name;
}
set name(newName: string) {
this._name = newName;
}
}

let hello = new Hello();
hello.name = "踏浪";
console.log(hello.say())

静态属性 static

static 也是添加在属性或者方法前面的,如下

1
2
3
4
5
6
class Hello {
static name: string = "踏浪";
say() {
return `I am ${this.name}`;
}
}

上面一段代码会出错

https://pic-go-1253455210.cos.ap-chengdu.myqcloud.com/blog/20201030110945.png

name 是静态成员,不同通过 [this.name](http://this.name) 访问,需要使用类调用

1
2
3
4
5
6
class Hello {
static name: string = "踏浪";
say() {
return `I am ${Hello.name}`;
}
}

接口 Interface

先来看一段代码

1
2
3
4
5
6
function sayPersonName(person: {name: string}) {
console.log(person.name);
}

let person = { name: '踏浪', age: 18 };
sayPersonName(person);

上面代码中 sayPersonName 的参数接收一个对象,对象的 name 属性是 string 类型的。这只是一个简单的例子,但是如果对象有很多的属性,每个属性都有各自的类型指定。那么按照上面的写法写出来的代码看着就不舒服,所以,有了接口的概念。

使用 interface 关键字来定义一个接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface person {
name: string;
age: number;
ID: number;
}

function sayPersonName(person: person) {
console.log(`I am ${person.name}, ${person.age} years old, ID is ${person.ID}`);
}

let person = {
name: 'Talang',
age: 18,
ID: 10000
};

sayPersonName(person);

上面就是使用接口的例子,例子中有多个 person ,希望你能明报每个 person 所代表的含义。

第一个 interfaceperson 是定义的一个 person 接口,指定一个对象以及对象每个属性的类型。

第二个是 sayPersonName 中的两个 person ,第一个是指函数接收的形式参数,第二个是形式参数的类型,就是上面使用 interface 定义个接口类型。

第三个是 let 声明的 person 变量。

第四个是函数sayPersonName 调用传递的 let 定义的这个对象。

这就是接口的最基本的使用。定义的接口的属性,在传递参数的时候需要全部都要确定。如果你试过声明 person 的时候不传递某个属性,就会出错。那么有什么方法改善呢。这就需要用到可选属性了。

可选属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface person {
name: string;
age: number;
ID: number;
}

function sayPersonName(person: person) {
console.log(`I am ${person.name}, ${person.age} years old`);
}

let person = {
name: 'Talang',
age: 18,
// ID: 10000
};

sayPersonName(person);

上面的代码编译的时候会出错 Property 'ID' is missing in type '{ name: string; age: number; }' but required in type 'person'.

前面讲函数参数的时候涉及到可选参数,使用 ?: 的形式表示的,其实,这里也是一样的。

1
2
3
4
5
6
interface person {
name: string;
age: number;
ID?: number;
}
...

这样定义就不会出错了

函数接口的定义

1
2
3
4
5
6
7
interface SearchFunction {
(keyword: string, page: number): string;
}

let mySearch: SearchFunction = function(keyword: string, page: number) {
return page > 10 ? keyword : 'false';
}

(keyword: string, page: number): string; 就是定义的函数的类型,参数以及返回值。

数组类型

1
2
3
4
5
interface stringArray {
[index: number]: string
}

let arr: stringArray = ['10', '10'];

数组的每一项都要是 string 类型的。

类的接口

声明类的时候要使用定义的接口需要使用 implements 关键字,声明的类中的属性需要有接口中的参数

1
2
3
4
5
6
7
8
interface ClockInterface {
currentTime: Date;
}

class Clock implements ClockInterface {
currentTime: Date = new Date();
constructor(h: number, m: number) {}
}

接口混合,继承

接口也是可以继承的

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 Color {
color: string;
}

interface Shape {
shape: string;
}

interface Pos {
x: number;
y: number;
}

interface Box extends Color, Shape { // 继承多个使用 , 隔开
position: Pos;
size: number;
}

let position: Pos = {
x: 100,
y: 100
}
let box: Box;
box = {
color: "red",
shape: "circle",
position: position,
size: 100
};

泛型

1
2
3
function Hello(num: number): number {
return num;
}

上面定义了一个函数 Hello 。参数与返回值都是 number 。但是如果我要修改为 string 或者其他类型。那就需要重新写一个函数。

你或许认为可以使用 any 类型,但是如果使用 any 那么就失去了类型规范与校验的功能。所以这时候就需要用到泛型了。

1
2
3
function Hello<T>(arg: T):T {
return arg;
}

泛型的写法 <T> 中间的大写字母为 T 是约定俗成的。

上面的函数在声明的时候使用了泛型,在使用的时候在指定具体的类型。

1
let output = Hello<string>('踏浪');

<> 中的类型与参数的类型需要一致,因为声明的函数类型是一样的。当然,在定义函数的时候,参数、返回值也可以是其他的类型。

泛型类型

1
2
3
4
5
6
7
8
9
10
11
12
function Hello<T>(arg: T):T {
return arg;
}

let myHello: <T>(arg: T) => T = Hello;
console.log(myHello<string>("Hello"));

let myHello2: {<T>(arg: T):T} = Hello;
console.log(myHello2<string>("Hello2"));

let myHello3 = Hello;
console.log(myHello3(10))

上面三种写法都可以,但是前两种会对新的函数做出类型检查,但是第三个函数确实不会的,直接传递也能编译成功,只是相当于执行了Hello函数而不做类型检查。

上面的前两个声明,我们可以定义一个接口

1
2
3
interface Hello<T> {
(arg: T): T;
}

或者这样写也是可以的

1
2
3
interface Hello {
<T>(arg: T): T;
}

泛型类

1
2
3
4
5
6
7
8
9
10
class Hello<T> {
name: T;
say: (name: T) => T;
constructor(name: T, say: (name: T) => T) {
this.name = name;
this.say = say;
}
}

let hello = new Hello<string>("踏浪", () => "hello");

Modules

TS中的模块与ES6中的模块差不多,可以看

Handbook - Modules

ES6 入门教程

需要关注的一点就是在TS中的通配符声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
declare module '*.gif' {
const content: any
export default content
}

declare module '*.png' {
const content: any
export default content
}

declare module '*.svg' {
const content: any
export default content
}

declare module '*.ttf' {
const content: any
export default content
}

这种声明允许在 TS 中 import 非 JavaScript 文件。

namespace

命名空间就像是声明一个对象,对象中还有其他属性,可以通过 . 操作符获取属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
declare namespace Person {
export interface Name {
name: string;
}

export interface Age {
age: number;
}

export class Color {
color: string;
}

interface Address {
address: string;
}

interface Id {
id: number;
}
}

使用的话可以直接 [Person.Name](http://person.Name) 来使用。在项目中,放在 .d.ts 文件中默认全局。如果没有使用 declare 关键字,需要手动 export

装饰器

装饰器就是在不调用函数的情况下执行函数。装饰器只能装饰类以及类的方法,属性

1
2
3
4
5
6
function hello() {
console.log('hello');
}

@hello
class Hello {}

上面的 @hello 就是一个装饰器,放在 class Hello 上面,是对 Hello 类的装饰。你还可以对属性,方法进行装饰,只需要放在那一行的上面即可。如果是一行,放在这一行的前面即可。

类的装饰

类的装饰,装饰器只能接收一个参数,参数指向这个类

1
2
3
4
5
6
7
8
9
10
@testable
class MyTestableClass {
// ...
}

function testable(target) {
target.isTestable = true;
}

MyTestableClass.isTestable // true

上面的装饰器为 MyTestableClass 类添加了一个静态属性 isTestable ,且值固定为 true 。那我可以手动的传递参数么?可以的。

1
2
3
4
5
6
7
8
9
10
11
12
@testable(true)
class MyTestableClass {
// ...
}

function testable(isTestable) {
return function (target) {
target.isTestable = isTestable
};
}

MyTestableClass.isTestable // true

方法的装饰

方法的装饰接收三个参数。装饰器第一个参数是类的原型对象,第二个参数是所要装饰的属性名,第三个参数是该属性的描述对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Person {
@readonly
name() { return `${this.first} ${this.last}` }
}

function readonly(target, propertyKey, descriptor){
// descriptor对象原来的值如下
// {
// value: specifiedFunction,
// enumerable: false,
// configurable: true,
// writable: true
// };
descriptor.writable = false;
return descriptor;
}

readonly(Person.prototype, 'name', descriptor);
// 类似于
Object.defineProperty(Person.prototype, 'name', descriptor);

访问器装饰

对于类的 gettersetter 也是可以进行装饰的,使用方法与类的方法的装饰一样

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
class Point {
private _x: number;
private _y: number;
constructor(x: number, y: number) {
this._x = x;
this._y = y;
}

@configurable(false)
get x() {
return this._x;
}

@configurable(false)
get y() {
return this._y;
}
}

function configurable(value: boolean) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
descriptor.configurable = value;
};
}

属性的装饰

属性的装饰一般很少用到。可以看看

Handbook - Decorators

ES6中也有装饰器的概念

ES6 入门教程

Mixins

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
// Disposable Mixin
class Disposable {
isDisposed: boolean;
dispose() {
this.isDisposed = true;
}

}

// Activatable Mixin
class Activatable {
isActive: boolean;
activate() {
this.isActive = true;
}
deactivate() {
this.isActive = false;
}
}

// 因为用implements,需要在子类里实现所有接口定义。非常繁琐
class SmartObject implements Disposable, Activatable {
constructor() {
setInterval(() => console.log(this.isActive + " : " + this.isDisposed), 500);
}

interact() {
this.activate();
}

// Disposable
isDisposed: boolean = false;
dispose: () => void;
// Activatable
isActive: boolean = false;
activate: () => void;
deactivate: () => void;
}
applyMixins(SmartObject, [Disposable, Activatable]);

let smartObj = new SmartObject();
setTimeout(() => smartObj.interact(), 1000);

////////////////////////////////////////
// In your runtime library somewhere
////////////////////////////////////////

function applyMixins(derivedCtor: any, baseCtors: any[]) {
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
derivedCtor.prototype[name] = baseCtor.prototype[name];
});
});
}

关键点在于 implementsSmartObject 中的部分属性只是占位没有具体实现,而是通过 Mixins 而混合而成。相当于一直继承的简写。

以上就是关于TS中常用到的,部分例子来自于官网,这里只是学习总结,以加深印象,方便查阅。

更新于2021-6-10

typescript帮助类型

ts中提供了一些好用的帮助类,可以大大提供我们的工作效率。

Partial

1
2
3
4
5
6
/**
* Make all properties in T optional
*/
type Partial<T> = {
[P in keyof T]?: T[P];
};

这个工具接受一个类型并返回一个类型。作用就是将传入类型中的所有参数都变成可选的。

上面又一个 P in keyof T 。看着这个有没有熟悉?像是 for…in 循环。没错。这就是 TS 中的 for…in 。遍历范型 T 中的属性命名为 P, T[P] 就是取值(TS中的值就是类型 string | number | …)。

Pick

1
2
3
4
5
6
/**
* From T, pick a set of properties whose keys are in the union K
*/
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};

接收两个参数,第一个是一个范型 T ,第二个是 T 中的属性,T中不存在这个属性,那么就会报错

1
2
3
4
5
6
7
8
9
10
11
interface Animal {
name: string
age: number
address: string
id: number
}

type Data1 = Pick<Animal, 'name'>
// Data1 {name: string}
type Data2 = Pick<Animal, 'name'| 'age'>
// Data2 {name: string; age: number}

最后返回的是一个保护第二个参数的属性的类型。就有点像是从一个类型中选择一个或者个几个属性出来

Exclude

1
2
3
4
/**
* Exclude from T those types that are assignable to U
*/
type Exclude<T, U> = T extends U ? never : T;

表示如果 T 是 U 的子类返回never类型,如果不是返回T类型。
当 T 为联合类型的时候,它会自动分发条件。

Omit

1
2
3
4
/**
* Construct a type with the properties of T except for those in type K.
*/
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

Omit 与 Pick 恰恰相反。 Pick 是选择你指定的属性,而 Omit 则是选择的你写的属性之外的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Animal {
name: string
age: number
address: string
id: number
}

interface Cat {
name: string
age: number
}

type Data = Omit<Animal, 'name' | 'age'>
// Data {address: string; id: number} 除了 name 和 age

常用的就是这些,很有一些其他的属性。可以参见 https://www.typescriptlang.org/docs/handbook/utility-types.html

查看 TS 文档建议去官网,而不是中文翻译网站

文章作者: 踏浪
文章链接: https://www.lyt007.cn/技术/Typescript笔记.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 平凡的生活,不平凡的人生
支付宝
微信打赏