typescript-函数/类/接口

接口类型

对象类型接口


假设我要从后端获取一组数据,渲染到页面之中,我们可以这样定义:

interface List {
  id:number;
  name:string;
}

interface Result {
    data:List[]
}

function render (result:Result) {
  result.data.forEach((value) => {
    console.log(value.id,value.name)
  })
}

let result = {
  data : [
    {"id":1,"name":"A"},
    {"id":2,"name":"B"},
  ]
}

render(reslut)

这样就定义了一个接口类型——规范
但是ts使用了duck type

// 如果data是这样也不回报错
let result = {
  data : [
    {"id":1,"name":"A", sex:1},
    {"id":2,"name":"B"},
  ]
}

render(result)

由于采用了duck type 也就是鸭子类型,输入的参数只要“长的像鸭子,那么它就是鸭子”——只需要满足interface必要条件, 就ok

但是如果直接传值,不声明变量:就无法通过接口类型检查

// 这样是无法通过编译的
render({
  data : [
    {"id":1,"name":"A", sex:1},
    {"id":2,"name":"B"},
  ]
}
)

那如果想解决?出了赋值给一个变量进行类型断言
还可以通过as关键字进行断言

// 加上as断言,就可以绕过类型检查
render({
  data : [
    {"id":1,"name":"A", sex:1},
    {"id":2,"name":"B"},
  ]
} as Result
)
// 这种也行,但是不推荐,react中会产生歧义
render(<Result>{
  data : [
    {"id":1,"name":"A", sex:1},
    {"id":2,"name":"B"},
  ]
}
)

还可以使用索引签名:

interface List {
  readonly id:number; // id 只读
  name:string;
  age?:int;  // 该参数表示可有可无
  [x:string]:any
}

不确定一个接口中有多少属性:使用可索引类型的接口

既可以使用数字,也可以使用字符串

// 用任意的数字去索引stringarray,都会得到一个string, 相当于声明了一个字符串类型的数组
interface StringArray {
    [index: number]: string
}
let char:StringArray = ["A", "B"]

// 用字符串去索引一个接口
//  用任意的字符串去索引Names, 得到的结果都是string
interface Names {
    [x:string]:string; //  这样声明之后,就不能声明number类型的成员
    // y:number  这样是不被允许的
    [z: number]:string // 这样我们既可以用数字,也可以用字符串索引Names
    // 需要注意的是:数字索引的返回值,一定要是字符串类型索引的子类型,因为js会进行类型转换,将number转换为string,这样会保持类型的兼容性
    // [z:number]:number   // 这样就和string不兼容了, 如果要兼容,可以将 [x:string]:string 改为  [x:string]:any
}

接口定义函数

我们可以用一个变量定义函数

let add:(x:number,y:number) => number

我们可以用接口定义它 => 等价变量定义

interface Add {
    (x:number,y:number):number
}

我们还可以使用类型别名定义

type Add = {x:number,ty:number} => number
let add:Add = (x,y) => a + b

混合类型接口

解释:既可以定义一个函数,也可以像对象一样拥有属性和方法

interface Lib {
    ():void;  // 首先定义一个函数,假设没有返回值和参数
	version:string;
	doSomething(): viod;
}

定义好了接口,我们如何进行实现?

let lib:Lib = (() => {}) as Lib  // 进行断言
lib.version = '1.0'
lib.doSomething = () => {}

上面这样定义的lib属于暴露全局的,且是单列,如果像创建多个,可以进行‘域’限定:函数封装~

function getLib {
    let lib:Lib = (() => {}) as Lib  // 进行断言
	lib.version = '1.0'
	lib.doSomething = () => {}
    return lib
}
lib1 = getLib();
lib1.doSomething();
lib2 = getLib();
lib3 = getLib();
lib4 = getLib();

函数

函数的定义

方法1
通过关键字 function

function add(x:number, y:number){
  return x + y
}

方法2
通过一个变量来定义一个函数类型

let add:(x:number,y:number) => number

方法3
通过类型别名type定义一个函数类型

type add=(x:number,y:number) => number

方法4
通过interface来定义一个函数

interface add {
  (x:number,y:number):number
}

ts的参数需要一一对应,

可选参数

? 表示该参数是可选参数

function add(x:number,y?:number) {
// do sth
}

注意:可选参数必须位于必选参数之后!~ 类似于python的关键字参数必须在位置参数之后

默认参数

类似python里的关键字参数

function add(x:number,y = 0, z:number,q = 1) {
    return x + y + z + q
}

add(1, undefined,3) // 5

必选参数之前的默认参数是必须要传值的,在必选参数之后的默认参数是不传的

剩余参数 (…)

和es6一样

function add(x:number,...rest:number[]){
  return x + rest.reduce((pre,cur) => pre + cur )
}

add(1,2,3,4,5) // 15

函数重载

实现一个方法,如果参数都是数字,就累加,如果参数都是字符串,就连接
首先ts和其他语言不同,需要先声明多个函数对象,在实现重载

function add(...ret:number[]):number;
function add(...ret:string[]):string;
function add(...ret:any[]):any{
  let first = ret[0]
  if (typeof first == 'string') {
    return ret.join('')
  }
  if (typeof first == 'number') {
    return ret.reduce((pre,cur) => pre + cur )
  }
}

由于重载是按顺序查询函数列表,应该把最容易出现的数据类型的函数,写在最前面

实现一个类

ts中引入了class关键字,覆盖了es6中的类,也增加了一些特性。
先实现一个类:

class Dog {
  constructor (name:string) {
  // constructor 的返回值是Dog 类型,也就是实例本身this
    this.name = name
  }
  name:string
  run() {}
}

这里要注意:
ts里类的属性都是实例属性,而不是原型属性(prototype
ts里类成员方法都是实例方法
实例的属性!必须有初始化的值

类的继承——extends

其实更像是拓展的意思

class Husky extends Dog {
  constructor (name:string,color:string ) {
    super(name)
    this.color = color // this 必须在super之后调用
  }
  color:string
}

类的继承,会提示我们“派生类的构造函数必须包含super调用”,super代表父类的实例

ts的修饰符——对es6拓展

默认所有属性都有public声明,也可以显式声明
private声明的属性,只能被类本身调用,不能被子类和实例调用
如果给构造函数constructor使用private声明,那么表示这个类,不能被继承,也不能被实例化
protected:受保护成员,一个受保护成员,只能被类或者子类调用,而不能被实例调用,如果给构造函数constructor添加保护,那该类不能被实例化,只能被继承
readonly:只读属性,不多哔哩吧啦,一定要初始化,和实例属性是一样的
static:这种属性,只能通过类名来调用,不能通过实例调用,子类也可以调用

此外,还可以给构造函数的参数添加修饰符,使其成为实例属性 ,代码会更简洁一些,就不用在外头再声明了

抽象类:abstract

es中没有抽象类,ts对es进行了拓展,引入了抽象类:只能被继承,而不能被实例化的类,实现的方法可被子类使用,定义的抽象方法,子类必须实现(明确知道子类自己会实现,就没必要在父类进行实现了)
举例:

abstract  class Animal {
 eat (){
   // do sth
 }
 abstract  sleep():void
 
 
}

class Dog extends Animal {
  constructor (name:string) {
    super()
    this.name = name
  }
  name:string
  run() {}
  sleep () {
  // do sth 
  }
}
  • 抽象共性,提取代码共性,提高复用性
  • 用于实现多态:抽象方法在子类中不同的实现——同时多态也是这种oop的核心

this

对于返回了this的方法,可以实现链式调用~
这其实很好理解,就是方法返回了实例
在js里很常见
可以运用都不同的编程语言中
同样,this同样表现为多态

类和接口的关系:implements

在有些语言里,是没有interface类型的,只能通过class进行表现
看代码:一个接口可以约束类成员有哪些属性

interface Human {
  name:string;
  eat():void
}
// 用类来实现了接口
// 必须实现接口中声明的所有的属性
// class也可以增加自己的属性
// 接口只能约束类的公有成员
// 接口不能约束构造函数 
class Asian implements Human {
  constructor(name:string) {
    this.name = name;
  }
  name:string
  eat(){}
  sleep(){}
}

接口继承:extends

接口可像class 一样被继承,且可继承多个接口
以上代码还有效的

interface Man extends Human {
  run():void;
}

interface Child {
  cry():void;
}

interface Boy extend Man,Child {
 
}

let boy:Boy={ 
  name:"",
  run(){},
  eat(){},
  cry(){}
}

可以看出接口可以拆分出可以重用的接口
也可合并为一个接口

接口继承类

接口除了可以继承接口
还可以继承类:相当于 接口把类的成员都抽象类出来,只有类的成员结构,而没有具体的实现

class Auto {
  state = 1
  // private state2 = 0
}

interface AutoInterface extends Auto {
 // 啥也不写,相当于这个接口中隐含了state属性
}

class C implements AutoInterface {
  state = 1
}
// 子类
class Bus extends Auto implements AutoInterface {
  // 这里不需要实现state属性,因为是Auto的子类
}

接口在抽离类成员的时候,不仅仅抽离类公共成员,而且抽离了私有成员和受保护成员

总结interface和 class

typescript-函数/类/接口_第1张图片

总结

  • 用类来实现了接口
  • 必须实现接口中声明的所有的属性
  • class也可以增加自己的属性
  • 接口只能约束类的公有成员
  • 接口不能约束构造函数

后续泛型还会在看到,ts的灵活性

你可能感兴趣的:(编程语言,前端,ts,typescript,接口,类,继承)