ts的学习心得: 接口与类

279 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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的函数组件类型:

image.png

对象字面量的错误

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() {}
}