TypeScript-Class(2)

440 阅读5分钟

TypeScript-Class(2)

只读修饰符

就像我们之前学习的接口(Interface )时可以用 readonly 修饰接口的属性一样,我们也可以用 readonly 修饰类的属性,比如我们动物的简介一旦确定就不会变了,我们可以这样来写:

class Animal {
  readonly brief: string = '动物是多细胞真核生命体中的一大类群,但是不同于微生物。';
  // ...其他一样
}

除了属性,我们还可以用 readonly 来修饰类中方法的参数,比如我们在设置此动物的类型时,一般可以给一个默认的类型:

class Animal {
  kindstring;
  static staticKindstring;
  constructor(kind:string){
    this.kind = kind
  }
  
  setKind(kind: string ="哺乳动物") {
    this.kind = kind;
    console.warn(this.kind)
  }

  static setKindStatic(staticKind:string='非哺乳动物'){
     this.staticKind = staticKind;
     console.warn(this.staticKind'this.staticKind')
  }

  static setKindStatic2(staticKind:stringreadonly defaultType = '哺乳动物'){
     this.staticKind = staticKind || defaultType ;
     console.warn(this.staticKind'this.staticKind')
  }
}
const dog = new Animal('11')
dog.setKind()

Animal.setKindStatic('所有实例都会改变为非哺乳动物')

Animal.setKindStatic2()

抽象类

抽象类与抽象方法

TS 另外一个特性就是抽象类,什么是抽象类了?我们来看个例子:

abstract class Animal {
  abstract makeSound(): void;
  move(): void {
   console.log("Roaming the earth...");
  }
}


可以看到抽象类就是在类之前加上 abstract 关键字,同时,它还不允许被实例化,也就是说如下的操作是不允许的:

const bird = new Animal() // Error

除此之外,抽象类相比普通类还有一个额外的特性就是,可以在抽象类中定义抽象方法,就像我们上面的 makeSound 方法,在普通的方法定义之前加上 abstract 关键字,这个抽象方法类似于接口里面的方法的类型定义:1)注解参数和返回值 2)不给出具体的实现,如上面的 move 就是存在具体的实现,而 makeSound 不给出具体的实现。

抽象类的继承

抽象类只可以被继承,不可以被实例化,且抽象类的继承与普通类也存在不同,普通类的继承可以只是简单的继承,并不需要额外的操作:

class Animal {
// Animal 相关的属性
}
class Bird extends Animal {
// 不需要做任何操作
}

但是如果一个类继承另外一个抽象类,那么它必须得实现抽象类中的抽象方法:

abstract class Animal {
  abstract makeSound(): void;
  move(): void {
   console.log("Roaming the earth...");
  }
}
class Bird extends Animal {
 makeSound(): void {
   console.log('Tuture tuture tuture.');
  }
}

可以看到,上面我们定义了一个 Bird 类,它继承自 Animal 抽象类,它必须得实现 makeSound 抽象方法。

构造函数

通过上面的讲解我们基本了解了 TS 中的类相比 JS 额外增加的特性,主要是讲解了如何注解类的相关部分内容,接下来我们着重来谈一谈如何用类来注解其他内容。这里为什么类可以作为类型来注解其他内容了?原来在 TS 中声明一个类的同时会创建多个声明:
1)第一个声明是一个类型,这个类型是这个类实例对象类型,用于注解类的实例对象。
2)第二个声明则是类的构造函数,我们在实例化类时,就是通过 new 关键字加上这个构造函数调用来生成一个类的实例。
声明注解类实例的类型
可能上面的概念听得有点懵,我们拿之前那个例子来实际演示一下。

class Animal {
  namestring;
  static isAnimal(aAnimal): boolean {
    const isFlag = a instanceof Animal
    console.warn(isFlag)
    return isFlag;
  }
  constructor(name: string) {
    this.name = name;
  }
  move(distance: number) {
    console.log(`Animal moved ${distance}m`);
  }
}
const birdAnimal = new Animal('Tuture');
Animal.isAnimal(bird)
bird.move(12)

这第一个声明的用于注解类实例对象的类型就是我们上面的 Animal ,当我们声明了一个 Animal 类之后,我们可以用这个 Animal 来注解 Animal 的实例如 bird 或者 isAnimal 方法中的 a 参数,当你理解了这个概念之后,你会发现 isAnimal 方法只允许传入为 Animal 实例的参数 a ,然后返回一个 a instance Animal 的布尔值,这是一个永远返回 true 的函数。

提示
这里这个声明的 Animal 类型不包括构造函数 constructor 以及类中的静态方法和静态属性,就像实例对象中是不包含类的构造函数、静态方法和静态属性一样。

声明构造函数
了解了第一个声明,那么第二个声明又是什么意思了?其实就是上面我们执行 new Animal('Tuture') 来生成一个实例时,这里的 Animal 实际上就是一个构造函数,通过 new Animal('Tuture') 调用实际上就是调用我们类里面的 constructor 函数。
那么有的同学看到这里就有疑问了,我们的 Animal 类型是用来注解类的实例的,那么类的构造函数 Animal 该如何注解了?我们来看这样一个例子:

let AnimalCreator = Animal;

在这段代码中,我们将 Animal  构造函数赋值给 AnimalCreator ,那么我们如何注解这个 AnimalCreator 变量的类型了?当然 TS 具有自动类型推导机制,一般情况下我们是不需要注解这个变量的,但这里如果我们要注解它,那么该如何注解了?答案是可以借助 JS 原有的 typeof 方法:

let AnimalCreator: typeof Animal = Animal;

我们通过 typeof Animal 获取构造函数 Animal 的类型,然后用此类型注解 AnimalCreator 。

类与接口

上面我们了解了类在声明的时候会声明一个类型,此类型可以用于注解类的实例,其实这个类型和我们之前学习的接口(Interface )有异曲同工之妙,具体类与接口结合使用的时候有如下场景:

  • 类实现接口

  • 接口继承类

  • 类作为接口使用


类实现接口
类一般只能继承类,但是多个不同的类如果共有一些属性或者方法时,就可以用接口来定义这些属性或者方法,然后多个类来继承这个接口,以达到属性和方法复用的目的,比如我们有两个类 Door (门)和 Car (车),他们都有 Alarm (报警器)的功能,但是他们又是不同的类,这个时候我们就可以定义一个 Alarm 接口:

interface Alarm {
 alert(): void;
}
class Car implements Alarm {
  alert() {
   console.log('Car alarm');
  }
}
class Door implements Alarm {
  alert() {
   console.log('Door alarm');
  }
}

此时的接口 Alarm 和我们之前定义的抽象类类似,接口中的方法 alert 类似抽象类中的抽象方法,一旦类实现 (implements )了这个接口,那么也要实现这个接口中的方法,比如这里的 alert 。
和类的单继承不一样,一个类可以实现多个接口,比如我们的车还可以开灯,那么我们可以定义一个 Light 接口,给车整上灯:(接口主要是用来约束模型,比如继承实现这个接口就一定要有接口定义的方法跟变量)

interface Alarm {
  alert(): void;
}
interface Light {
  lightOn(): void;
  lightOff(): void;
}
class Car implements AlarmLight {
  alert() {
    console.log('Car alarm');
  }
  lightOn() {
    console.log('Car lighton');
  }
  lightOff() {
    console.log('Car lightoff');
  }
}

const car = new Car()
car.lightOn()

接口继承类
接口之所以可以继承类是因为我们之前说到了类在声明的时候会声明一个类型,此类型用于注解类的实例。而接口继承类就是继承这个声明的类型,我们来看一个例子:

class Point {
  x: number = 0;
  y: number = 0;
}
interface Point3d extends Point {
  z: number;
}
let point3d: Point3d = { x: 1, y: 2, z: 3 };
console.warn(point3d)

可以看到,接口 Point3d 继承自类 Point ,获取了来自类的 x 和 y 属性,实际上接口继承的是声明 Point 类时同时声明的用于注解类实例的那个类型,而这个类型只包含类的实例属性和方法,所以接口继承类也是继承此类的实例属性和方法的类型。
类作为接口使用
类作为接口使用的场景主要在我们给 React 组件的 Props 和 State 进行类型注解的时候,我们既要给组件的 Props 进行类型注解,有时候还要设置组件的 defaultProps 值,这里的 Props 的注解和 defaultProps 值设置原本需要分开进行,我们来看一个例子:

interface TodoInputProps {
  valuestring;
  onChange(value: string) => void;
}
interface TodoInputState {
  contentstring;
  userstring;
  datestring;
}
const hardCodeDefaultProps = {
  value'tuture',
  onChange(value: string) { console.log(Hello ${value}); }
}
class TodoInput extends React.Component<TodoInputPropsTodoInputState> {
  static defaultPropsTodoInputProps = hardCodeDefaultProps;
  render() {
   return ;
  }
}

可以看到,上面是一个标准的 React 类组件,我们通过 React.Component<TodoInputProps, TodoInputState> 的形式注解了这个类组件的 Props 和 State ,通过声明了两个接口来进行注解,这里 React.Component<TodoInputProps, TodoInputState> 就是泛型,这里可以理解泛型类似 JS 函数,这里的 <> 类似函数的 () ,然后可以接收参数,这里我们传入了两个参数分别注解类的 Props 和 State 。
我们还注意到,我们声明了这个类的 defaultProps ,然后定义了一个 hardCodeDefaultProps 来初始化这个 defaultProps 。
这就是常见的 React 类组件的类型注解和默认参数初始化的场景,但是当我们学了类之后,我们可以简化一下上面的类组件的类型注解和默认参数初始化的操作:

class TodoInputProps {
  valuestring = 'tuture';
  onChange(value: string) {
   console.log('Hello Tuture');
  }
}
interface TodoInputState {
  contentstring;
  userstring;
  datestring;
}
class TodoInput extends React.Component<TodoInputPropsTodoInputState> {
  static defaultPropsTodoInputProps = new TodoInputProps();
  render() {
   return ;
  }
}

可以看到,上面我们将接口 Props 换成了类 TodoInputProps ,这带来了一些改变,就是类里面可以给出属性和方法的具体实现,而我们又知道声明类 TodoInputProps 的时候会同时声明一个类型 TodoInputProps ,我们用这个类型来注解组件的 Props ,然后注解 defaultProps ,然后我们用声明类时声明的第二个内容:TodoInputProps 构造函数来创建一个 TodoInputProps 类型的实例对象并赋值给 defaultProps