003_TypeScript 第四章

descript

  • 通过前两章的学习, 我们基本上对于 TS 已经入门了
  • 但是我们会发现, 我们好像对于类型限制还缺少了一些内容
  • 这一章我们继续学习

接口

  • 初学者刚一听到这个名字, 可能不太好理解
  • 接触过前后端交互的可能会对这个名字比较迷茫, 容易混乱
  • 但是, 都不要纠结, 忘记你之前所有的一切, 他就是一个名字而已

descript

  • 就像这个玩意, 老婆饼, 老婆在哪里, 就是个名字而已
  • 那么接口到底是干什么的呢 ?
    • 其实就是一个用来限制非基本数据的类型限制手段而已
  • 我们回忆之前的类型限制, 我们发现
    • 对于 对象, 函数, 类 等内容并没有很好的限制方式
    • 难道真的没有, 并不是
    • 接口就是其中一个

接口初体验

  • 来看下面这个例子
1
let user: { name: string } = {}

descript

  • 所以这一段代码就会提示错误

descript

  • 那么我们就需要修改代码, 赋值的时候需要有一个 name 属性存在
1
let user: { name: string } = { name: '许小墨' }
  • 这样就没有问题了
  • 再继续看下一个例子
1
2
3
let user: { name: string } = { name: '许小墨' }

let person: { name: string } = { name: 'hello world' }
  • 我们会发现 user 变量和 person 变量的类型限制使用的是一模一样的内容
    • 但是我需要逐次书写
    • 按照我们程序员的懒惰特点来看, 这个东西为什么不能只写一遍呢 ?
    • 一定是可以的
  • 这个东西就叫做 接口
    • 我们可以把这一种类型限制, 拿出来写成一个接口
    • 用起来就方便多了
  • 接口的形式书写
1
2
3
4
5
6
7
interface Info {
name: string
}

let user: Info = { name: '千锋大前端' }

let person: Info = { name: '前端培训界扛把子' }
  • interface : TS 中定义接口的关键字
  • Info : 接口名称( 建议首字母大写 )
  • 是不是一下子豁然开朗了

descript

接口的各类属性

基础接口属性

  • 就是对接口内的各种属性进行数据类型限制
1
2
3
4
5
6
interface Info {
name: string
age: number
gender: boolean
hobby: string[]
}
  • 这个接口就是一个基础接口限制
    • 必须包含一个 name 属性, 而且是 string 类型
    • 必须包含一个 age 属性, 而且是 number 类型
    • 必须包含一个 gender 属性, 而且是 boolean 类型
    • 必须包含一个 hobby 属性, 而且是由 string 组成的数组

选填属性

  • 我们可以设置一些属性选填
1
2
3
4
5
6
7
interface Info {
name: string
age: number
gender: boolean
hobby: string[]
city?: string
}
  • 这里就是设置了一个可选属性
    • city 属性可有可无, 如果添加了 city 属性, 必须是 string 类型

只读属性

  • 我们也可以设置一些属性不允许被修改
  • 就需要在接口内写属性的时候, 用到 readonly 关键字
1
2
3
4
5
6
7
8
interface Info {
name: string
age: number
gender: boolean
hobby: string[]
city?: string
readonly nationality: string
}
  • 这样定义完毕以后, nationality 属性值定义好以后就不能修改了

额外属性

  • 到这里我们会发现一个问题
    • 那就是我要是用接口限制一个数据, 那么必须要把所有可能会出现的属性都写上
    • 但是万一我只能确定部分属性, 其他的不确定怎么办呢
  • 这个时候可以用到额外属性
1
2
3
4
5
6
7
8
9
interface Info {
name: string
age: number
gender: boolean
hobby: string[]
city?: string
readonly nationality: string
[props: string]: any
}
  • 这就表示, 除了这里已经列出来的属性, 还可以添加其他的键值对
    • 但是 key 必须是字符串类型
    • 值可以是任意类型
  • 这个额外属性其实也不太建议使用
    • 第一: 我们最好准确的把握我们自己写的数据类型限制
    • 第二: 这样一来, 我们的限制其是意义就并不是很大了

descript

索引型接口

  • 索引型接口一般就是用接口去约束一个数组
1
2
3
interface StringArray {
[index: number]: string
}
  • 我们先来分析这个接口
    • 用的是额外属性的方式
    • 可以添加任意个属性
    • 但是 key 必须是一个 number 类型
      • 如果是对象数据结构, 那么 key 只能是 string 类型
      • 数组数据类型, key 才是 number 类型
    • 值必须是一个 string
  • 所以这个接口就限制了必须是一个由字符串组成的数组

descript

  • 其实这个就等价于我们限定数组的方式是一样的
1
2
3
4
5
6
7
interface StringArray {
[index: number]: string
}
let list: StringArray = ['hello']

// 等价于
let list2: string[] = ['world']

函数接口

  • 我们思考一下, 其实对于函数的限定无非就是两个部分
    • 参数
    • 返回值
  • 所以一个函数接口就是限制好函数的参数和返回值就好了
1
2
3
interface SearchFunc {
(x: number, y: string): string
}
  • 分析一波
    • 这个限制了需要有两个参数
      • 一个是 number 类型
      • 一个是 string 类型
    • 还限制了返回值
      • 必须是 string 类型
    • 那么这种数据类型, 就肯定是函数了
  • 但是这种接口一般用来限制 函数表达式
    • 对于声明式函数不适用
1
2
3
4
5
6
7
interface SearchFunc {
(x: number, y: string): string
}

const fn: SearchFunc = function (x, y) {
return x + y
}

descript

descript

接口继承

  • 接口也是可以实现继承的
  • 同样是使用 extends 关键字
1
2
3
4
5
6
7
8
9
interface Person {
name: string
age: 18
}

// Student 接口继承自 Person 接口
interface Student extends Person {
classRoom: number
}

descript

类接口

  • 我为什么把 类接口 放在最后面呢, 因为他非常复杂

descript

类的观察和分析

  • 咱们先来写一个简单的类分析一下
1
2
3
4
5
6
7
8
9
10
class Student {
constructor(name, age) {
this.name = name
this.age = age
}

sayHi() {
console.log(`Hello, My name is ${this.name}`)
}
}
  • 咱们来看一下, 如何才能对一个类做出限制
    • 限制好调用必须和 new 关键字连用
    • 类里面有一个 constructor 构造器, 需要接受参数并且限定返回的实例是一个什么样的对象
    • 我们调用这个类的时候传递多少个参数, 分别是什么类型
    • 类的原型上有多少个方法
  • 需要这么多维度才能限制好一个类类型数据

类接口

  • 首先, 我们先要来学习一个新的接口规范, 就叫做类接口
  • 类接口是专门给类使用的
  • 先来看一下
1
2
3
4
5
6
7
8
9
10
11
// 对象接口, 限制实例对象
interface StudentInstance {
name: string
age: number
}

// 类接口, 限制构造器
interface StudentConstructor {
// 构造器的返回值必须要时一个符合 StudentInstance 实例限制的对象
new (name: string, age: number): StudentInstance
}
  • 给一个类使用接口, 需要使用 implements 关键字
1
2
3
4
5
6
class Student implements StudentConstructor {
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
  • 看上去好像很好的样子, 也很简单的嘛

descript

  • 但是写完才发现, 全是错误

descript

  • 我来帮你分析一下
    • StudentConstructor 接口, 看似是一个类接口, 但是其实只是限制了 new 关键字 和 返回值类型, 不能说他是一个类, 更像是一个函数, 所以我们那它去限制一个类显然不太好
    • Student 这个类, 不是函数, 是一个类, 里面的 constructor 才是一个构造器函数, 那么这个构造器在没有被调用的时候, 是没有实例的, 所以你没办法合理添加 name 和 age 属性
  • 全都是问题, 那我怎么办

descript

  • 这个时候, 我们要想更好的做出所有限制, 就需要借助工厂函数
    • 并且还需要两个接口
    • 一个限制实例对象
    • 一个限制构造器
  • 先不着急加入 TS 限制, 先把类写成工厂模式
1
2
3
4
5
6
// 定义工厂函数
// function createStudent( 类, 参数 1, 参数 2 ) {}

function createStudent(ctro, name, age) {
return new ctro(name, age)
}
  • 也就是说, 我们需要把类放进工厂函数内去调用即可
    • 我们的工厂函数需要接受一个 类, 并且还要接受这个类需要的 参数
    • 在工厂函数内进行实例化操作, 然后返回来
  • 接下来我们就可以加上接口限制了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 实例接口, 限制实例对象
interface StudentInstance {
name: string
age: number
sayHi(): void
}

// 类接口, 限制构造器
interface StudentConstructor {
new (name: string, age: number): StudentInstance
}

// 工厂函数
function createStudent(ctro: StudentConstructor, name: string, age: number): StudentInstance {
return new ctro(name, age)
}
  • 工厂函数的 ctro 参数, 必须要是一个满足 StudentConstructor 接口的类
  • 工厂函数的返回值必须是一个满足 StudentInstance 接口的实例对象
  • 通过这两点, 就限制了类的实例对象有的成员, 多了就不行
    • 因为工厂函数只能接受这些数据
    • 多了的话, 调用工厂函数就会出错了
  • 最后书写类
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
// 实例接口, 限制实例对象
interface StudentInstance {
name: string
age: number
sayHi(): void
}

// 类接口, 限制构造器
interface StudentConstructor {
new (name: string, age: number): StudentInstance
}

// 工厂函数
function createStudent(ctro: StudentConstructor, name: string, age: number): StudentInstance {
return new ctro(name, age)
}

// 定义类
class Person implements StudentConstructor {
name: string
age: number

constructor(name: string, age: number) {
this.name = name
this.age = age
}

sayHi(): void {}
}

// 开始使用
let s1 = createPerson(Student, '千锋大前端', 10)
console.log(s1)
  • 这时候, 一个真正的对类的限制就完成了
  • 确实是非常麻烦, 所以我们在开发过程中相对比较少的对 类进行 限制
  • 因为类其实本身就是一种限制