持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第9天,点击查看活动详情
本文是ts系列其中一篇,是总结自己在学习和工作中使用ts的心得感想,仅作为自己复习使用,如对您有启发不胜荣幸。
接口
下面的代码中,把 { name: string} 写了两次,这样很麻烦,所以可以使用interface把公共部分抽离:
function getPersonName(person: { name: string}) {
return person.name
}
function setPersonName(person: { name: string }, name: string) {
return person.name = name
}
// 修改
interface Person {
name: string
}
function getPersonName(person: Person) {
return person.name
}
function setPersonName(person: Person, name: string) {
return person.name = name
}
interface与type的区别
两者之间没有多大区别,但是type可以代表一个基础类型,interface就只能代表一个对象或者函数。
type 的含义是定义自定义类型,当 TS 提供给你的基础类型都不满足的时候,可以使用 type 自由组合出你的新类型,而 interface 应该是对外输出的接口。在ts的通用规范里面首先使用 interface 来表示,实在不行就用 type 来表示。
interface Person {
name: string
}
type Person = { name: string }
// type还可以代表基础类型
type Person1 = string
type 定义类型
// 基本类型
type UserName = string
// 类型赋值
type WebSite = string
type Tsaid = WebSite
// 对象
type User = {
name: string;
age: number;
website: WebSite;
}
// 方法
type say = (age: number) => string
鸭子类型
interface 还有个名字叫做duck typing 表示是一个鸭子类型,什么意思?
如果一个东西能长的像鸭子, 像鸭子一样游泳,一样叫,那么就可以认为这个东西就是一个鸭子。可以理解为,只要你实现了interface 的功能要求,那么你就是这个interface。下面我们用interface定义一个比较怪的类型。
变量 a 的本体是一个函数,但是变量也有一个属性是name。
interface FunctionWithProps {
(x: number): number;
name: string;
}
const a: FunctionWithProps = (x: number) => {
return x
}
a.name = '123'
其实这样的类型就是react的函数组件类型:
对象字面量的错误
interface Persion {
name: string
age?: number
}
function getPersonName(person: Persion) {
return person.name
}
// person1里面多了一个sex属性,作为参数传递进去是不会报错的,只要参数里面有name这个属性即可
const person1 = {
name: 'jack',
sex: 'female'
}
getPersonName(person1)
// 如果这么写就会报错,这是因为ts会对字面量进行强校验,不只是要求有name属性,也不能有多余的属性
getPersonName({
name: 'jack',
sex: 'female'
})
可索引的接口
使用场景: 接口定义的对象属性不确定。
interface Persion {
name: string
age: number // 报错
[propName: string]: string
}
// 修改
interface Persion1 {
name: string
age: number
// 改为any类型
[propName: string]: any
}
第一个接口的代码会报错,因为[propName: string]: string就已经明确了,所有的属性的返回值必须是字符串,现在age是number类型,所以会报错。
接口的使用
1. 接口里面不仅有属性,还可以有方法
interface Persion {
name: string
age?: number
// 方法
say(): string
}
2. 类实现接口 implement
interface Persion {
name: string
say(): string
}
class User implements Persion {
name = '123'
say() {
return '123'
}
}
// 编译成js
var User = /** @class */ (function () {
function User() {
this.name = '123';
}
User.prototype.say = function () {
return '123';
};
return User;
}());
3. 接口继承接口
interface Persion {
name: string
say(): string
}
interface Teacher extends Persion {
techer(): string
}
4. 接口定义函数
interface SayHi {
(word: string): string
}
SayHi 接口就是一个函数。
类
类: 定义属性和方法。
类的继承
class Person {
constructor() {
this.name = 'jack';
}
getName() {
return this.name;
}
}
class Teacher extends Person {
getTeacherName() {
return 'mack';
}
}
const teacher Teacher = new Teacher();
这里需要注意的是类不仅可以实例化一个对象,同时也可以作为类型使用,比如:
class Foo {}
const a: Foo = new Foo()
子类对父类的方法的改写(多态):
class Person {
getName() {
return this.name
}
}
class Teacher extends Person {
getName() {
return 'lee'
}
}
const teacher = new Teacher()
console.log(teacher.getName()) // lee
子类访问父类的方法:super
class Teacher extends Person {
...
getName() {
return super.getName() + 'lee'
}
}
构造器 constructor
构造器的作用:赋初始化的值。
class Person {
// 定义name的类型
name: string | undefined
// 通过构造器赋值
constructor(name: string) {
this.name = name
}
}
const person = new Person('jack')
console.log(person.name)
// 还有一种更简单的写法,推荐使用这种写法
class Person {
constructor(public name: string) {}
}
如果子类也有自己的构造器,那么必须要在构造器里面执行super函数,super就是把父类的构造器也执行一遍,同时传递父类构造器需要的参数。注意即使父类没有构造器,子类也必要要写super()。
类的可见性修饰符
- public:允许在类的内外被调用
- private:允许在类的内部被调用
- protected:它介于public和private之间,允许在类的内部以及继承的子类内部中使用
getter and setter
如果在某些场景下你的某些变量不想被外部直接访问,那么这个时候就需要使用get和set了。
定义了一个私有属性_name(私有属性一般用下滑线表示),外部不能直接访问,如果要访问就去访问get,修改的时候访问set。这样就可以对_name私有属性做一个保护。
class Person {
private _name: string
constructor(_name: string) {
this._name = _name
}
get name() {
return this._name
}
set name(name) {
this._name = name
}
}
const person = new Person('jack')
person.name = 'lee'
console.log(person.name)
静态属性 static
static:在类本身挂载属性或者方法。
利用static实现一个单例:
class Person {
private static instance: Person
private constructor(public name: string) {}
static getInstance() {
if (!this.instance) {
this.instance = new Person('dell')
}
return this.instance
}
}
// 转化为js:
var Person = (function () {
function Person(name) {
this.name = name;
}
Person.getInstance = function () {
if (!this.instance) {
this.instance = new Person('dell');
}
return this.instance;
};
return Person;
}());
var person1 = Person.getInstance();
var person2 = Person.getInstance();
- private static 可以同时写,那么instance这个属性既可以在内部访问,又可以挂载到类本身上。
- private constructor(){} 说明外部不能访问 constructor,也就是不能使用new Person,这样就只能通过getInstance才能创建实例。
抽象类
如果很多类有通用的东西,那么就可以把通用的东西定义成一个抽象类。
抽象类的特点:
- 抽象类是不能new的,只能被继承使用
- 抽象类里面不仅可以是具体的属性或者方法,也可是抽象的方法
- 如果继承了抽象类,且抽象类里面有抽象方法,那么子类必须要是实现这个抽象方法
abstract class Geom {
// 定义抽象方法,也就是这个方法的实现是各种各样的,这里也不知道怎么实现,所以就定义为抽象的
abstract getArea(): number
// 也可以定义具体的方法或者属性
width: number | undefined
getType() {
return 'Geom'
}
}
class Circle extends Geom {
// 子类必须实现抽象方法
getArea() {
return 123
}
}
class 与 interface
通过一个例子来看看接口的威力。
首先定义一个具有闹钟功能的接口:
interface ClockInterface {
currentTime: number
alert(): void
}
定义一个闹钟的class:
class Clock implements ClockInterface {
currentTime: number = 123
alert() {}
}
如果这个时候手机也要实现ClockInterface的功能,如果你继承Clock,但是手机和Clock在认知中不属于同一类,怎么办呢?这个时候就可以使用interface, interface大大提高了灵活性。
class CellPhone implements ClockInterface {
currentTime: number = 123
alert() {}
}
如果还要实现游戏的功能
interface Game {
play(): void
}
class CellPhone implements ClockInterface, Game {
currentTime: number = 123
alert() {}
play() {}
}
可以看到 interface 非常灵活,它比 class 具有更多的使用场景。
上面,我们讲了如何用 interface 来定义 class,但是都是定义实例类型,那如何定义构造函数和静态属性呢?
interface ClockStatic {
// 加上new 因为类通过new来实例一个对象
new (x: number, y: number): void
time: number
}
// Clock: ClockStatic 表示对Clock本身的约束
// class Clock implements ClockInterface是对Clock实例的约束
const Clock: ClockStatic = class Clock implements ClockInterface {
constructor(x: number, y: number) {}
// 静态属性
static time = 132
currentTime: number = 123
alert() {}
}