1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class User {
// 定义成员属性
id: number
username: string

// 定义成员方法
postArticle(title: string, content: string) {
console.log(title, content)
}

// 构造函数
constructor(id: number, username: string) {
// 构造函数的作用:创建类的函数,当类实例化时被调用
console.log('构造函数')
this.id = id
this.username = username
}
}

const user = new User(1, '小康')
user.postArticle('title', 'content')

在ts中可以简化定义成员属性和赋值的夫过程

1
2
3
4
5
6
class User {
postArticle(title: string, content: string) {
console.log(title, content)
}
constructor(public id: number, public username: string) {}
}

继承

如果子类需要有自己的构造函数,那么需要手动调用父类构造函数。如果没有则不需要手动调用父类构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class User1 {
postArticle(title: string, content: string) {
console.log(title, content)
}
constructor(public id: number, public username: string) {}
}

class Vip extends User1 {
constructor(id: number, username: string, source: number) {
// 手动调用父类构造函数
super(id, username)
console.log('子类构造函数')
}
}

super 关键字

在子类中,我们可以通过 super 来引用父类

  • 如果子类没有重写构造函数,则会在默认的 constructor 中调用 super()

  • 如果子类有自己的构造函数,则需要在子类构造函数中显示的调用父类构造函数 : super(//参数),否则会报错

  • 在子类构造函数中只有在 super(//参数) 之后才能访问 this

  • 在子类中,可以通过 super 来访问父类的成员属性和方法

  • 通过 super 访问父类的的同时,会自动绑定上下文对象为当前子类 this

方法的重写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class VIP extends User {
constructor(
id: number,
username: string,
public score = 0
) {
super(id, username);
}

// postArticle 方法重写,覆盖
postArticle(title: string, content: string): void {
this.score++;
console.log(`${this.username} 发表了一篇文章: ${title},积分:${this.score}`);
}

postAttachment(file: string): void {
console.log(`${this.username} 上传了一个附件: ${file}`)
}
}

// 具体使用场景
let vip1 = new VIP(1, 'Leo');
vip1.postArticle('标题', '内容');

方法的重载

当父类方法过多时,没必要重新写一遍父类的方法,因此可以调用父类的方法。

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
class VIP extends User {

constructor(
id: number,
username: string,
public score = 0
) {
super(id, username);
}

// 参数个数,参数类型不同:重载
postArticle(title: string, content: string): void;
postArticle(title: string, content: string, file: string): void;
postArticle(title: string, content: string, file?: string) {
super.postArticle(title, content);

if (file) {
this.postAttachment(file);
}
}

postAttachment(file: string): void {
console.log(`${this.username} 上传了一个附件: ${file}`)
}
}

// 具体使用场景
let vip1 = new VIP(1, 'Leo');
vip1.postArticle('标题', '内容');
vip1.postArticle('标题', '内容', '1.png');

修饰符

有的时候,我们希望对类成员(属性、方法)进行一定的访问控制,来保证数据的安全,通过 类修饰符 可以做到这一点,目前 TypeScript 提供了四种修饰符:

修饰符含义自身子类类外
public公有,默认
protected受保护×
private私有××
readonly只读

只读修饰符只能针对成员属性使用,且必须在声明时或构造函数里被初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class User {

constructor(
// 可以访问,但是一旦确定不能修改
readonly id: number,
// 可以访问,但是不能外部修改
protected username: string,
// 外部包括子类不能访问,也不可修改
private password: string
) {
// ...
}
// ...
}

let user1 = new User(1, '小康', 'https://xiaokang.me');

寄存器

有的时候,我们需要对类成员 属性 进行更加细腻的控制,就可以使用 寄存器 来完成这个需求,通过 寄存器,我们可以对类成员属性的访问进行拦截并加以控制,更好的控制成员属性的设置和访问边界,寄存器分为两种:

  • getter
  • setter

getter

访问控制器,当访问指定成员属性时调用

setter- 组件

- 函数式组件

- 类式组件

- props 与 state

- 组件通信

- 表单与受控组件

设置控制器,当设置指定成员属性时调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class User {

constructor(
readonly _id: number,
readonly _username: string,
private _password: string
) {
}

public set password(password: string) {
if (password.length >= 6) {
this._password = password;
}
}

public get password() {
return '******';
}
// ...
}

静态成员

前面我们说到的是成员属性和方法都是实例对象的,但是有的时候,我们需要给类本身添加成员,区分某成员是静态还是实例的:

  • 该成员属性或方法是类型的特征还是实例化对象的特征
  • 如果一个成员方法中没有使用或依赖 this ,那么该方法就是静态的
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
type IAllowFileTypeList = 'png'|'gif'|'jpg'|'jpeg'|'webp';

class VIP extends User {

// static 必须在 readonly 之前
static readonly ALLOW_FILE_TYPE_LIST: Array<IAllowFileTypeList> = ['png','gif','jpg','jpeg','webp'];

constructor(
id: number,
username: string,
private _allowFileTypes: Array<IAllowFileTypeList>
) {
super(id, username);
}

info(): void {
// 类的静态成员都是使用 类名.静态成员 来访问
// VIP 这种类型的用户允许上传的所有类型有哪一些
console.log(VIP.ALLOW_FILE_TYPE_LIST);
// 当前这个 vip 用户允许上传类型有哪一些
console.log(this._allowFileTypes);
}
}

let vip1 = new VIP(1, 'zMouse', ['jpg','jpeg']);
// 类的静态成员都是使用 类名.静态成员 来访问
console.log(VIP.ALLOW_FILE_TYPE_LIST);
this.info();
  • 类的静态成员是属于类的,所以不能通过实例对象(包括 this)来进行访问,而是直接通过类名访问(不管是类内还是类外)
  • 静态成员也可以通过访问修饰符进行修饰
  • 静态成员属性一般约定(非规定)全大写

抽象类

抽象类约定了所有继承子类的所必须实现的方法,使类的设计更加的规范。

  • abstract 修饰的方法不能有方法体
  • 如果一个类有抽象方法,那么该类也必须为抽象的
  • 如果一个类是抽象的,那么就不能使用 new 进行实例化(因为抽象类表名该类有未实现的方法,所以不允许实例化)
  • 如果一个子类继承了一个抽象类,那么该子类就必须实现抽象类中的所有抽象方法,否则该类还得声明为抽象的

定义抽象类使用abstract关键字实现。

1
2
3
4
5
6
7
8
9
10
11
abstract class Component<T1, T2> {

public state: T2;

constructor(
public props: T1
) {
}

public abstract render(): string;
}

例如react的实现

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
abstract class Component<T1, T2> {
props: T1
state: T2

constructor(props: T1) {
this.props = props
}

abstract render(): string
}

interface IMyComponentProps {
val: number
}
interface IMyComponentState {
x: number
}
class MyComponent extends Component<IMyComponentProps, IMyComponentState> {
constructor(props: IMyComponentProps) {
super(props)

this.state = {
x: 1
}
}

render() {
this.props.val
this.state.x
return '<myComponent />'
}
}

let myComponent = new MyComponent({ val: 1 })
myComponent.render()

类与接口

当一个抽象类中只有抽象的时候,它就与接口没有太大区别了,这个时候,我们更推荐通过接口的方式来定义契约

  • 抽象类编译后还是会产生实体代码,而接口不会
  • TypeScript 只支持单继承,即一个子类只能有一个父类,但是一个类可以实现过个接口
  • 接口不能有实现,抽象类可以

implements

在一个类中使用接口并不是使用 extends 关键字,而是 implements

  • 与接口类似,如果一个类 implements 了一个接口,那么就必须实现该接口中定义的契约
  • 多个接口使用 , 分隔
  • implementsextends 可同时存在
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
interface ILog {
getInfo(): string;
}

class MyComponent extends Component<IMyComponentProps, IMyComponentState> implements ILog {

constructor(props: IMyComponentProps) {
super(props);

this.state = {
val: 1
}
}

render() {
this.props.title;
this.state.val;
return `<div>组件</div>`;
}

getInfo() {
return `组件:MyComponent,props:${this.props},state:${this.state}`;
}

}

实现多个接口

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
interface ILog {
getInfo(): string;
}
interface IStorage {
save(data: string): void;
}

class MyComponent extends Component<IMyComponentProps, IMyComponentState> implements ILog, IStorage {

constructor(props: IMyComponentProps) {
super(props);

this.state = {
val: 1
}
}

render() {
this.props.title;
this.state.val;
return `<div>组件</div>`;
}

getInfo(): string {
return `组件:MyComponent,props:${this.props},state:${this.state}`;
}

save(data: string) {
// ... 存储
}

}

接口也可以继承

1
2
3
4
5
6
interface ILog {
getInfo(): string;
}
interface IStorage extends ILog {
save(data: string): void;
}

类与对象类型

当我们在 TypeScript 定义一个类的时候,其实同时定义了两个不同的类型

  • 类类型(构造函数类型)
  • 对象类型

首先,对象类型好理解,就是我们的 new 出来的实例类型

那类类型是什么,我们知道 JavaScript 中的类,或者说是 TypeScript 中的类其实本质上还是一个函数,当然我们也称为构造函数,那么这个类或者构造函数本身也是有类型的,那么这个类型就是类的类型

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
class Person {
// 属于类的
static type = '人';

// 属于实例的
name: string;
age: number;
gender: string;

// 类的构造函数也是属于类的
constructor( name: string, age: number, gender: '男'|'女' = '男' ) {
this.name = name;
this.age = age;
this.gender = gender;
}

public eat(): void {
// ...
}

}

let p1 = new Person('zMouse', 35, '男');
p1.eat();
Person.type;

上面例子中,有两个不同的数据

  • Person 类(构造函数)
  • 通过 Person 实例化出来的对象 p1

对应的也有两种不同的类型

  • 实例的类型(Person
  • 构造函数的类型(typeof Person

用接口的方式描述如下

1
2
3
4
5
6
7
8
9
10
11
12
interface Person {
name: string;
age: number;
gender: string;
eat(): void;
}

interface PersonConstructor {
// new 表示它是一个构造函数
new (name: string, age: number, gender: '男'|'女'): PersonInstance;
type: string;
}

在使用的时候要格外注意

1
2
3
4
5
6
7
8
9
function fn1(arg: Person /*如果希望这里传入的Person 的实例对象*/) {
arg.eat();
}
fn1( new Person('', 1, '男') );

function fn2(arg: typeof Person /*如果希望传入的Person构造函数*/) {
new arg('', 1, '男');
}
fn2(Person);