类
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(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- 组件
- 函数式组件
- 类式组件
- 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 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 { console.log(VIP.ALLOW_FILE_TYPE_LIST); 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
了一个接口,那么就必须实现该接口中定义的契约 - 多个接口使用
,
分隔 implements
与 extends
可同时存在
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 (name: string, age: number, gender: '男'|'女'): PersonInstance; type: string; }
|
在使用的时候要格外注意
1 2 3 4 5 6 7 8 9
| function fn1(arg: Person ) { arg.eat(); } fn1( new Person('', 1, '男') );
function fn2(arg: typeof Person ) { new arg('', 1, '男'); } fn2(Person);
|