接口类型
对象类型接口
假设我要从后端获取一组数据,渲染到页面之中,我们可以这样定义:
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
总结
- 用类来实现了接口
- 必须实现接口中声明的所有的属性
- class也可以增加自己的属性
- 接口只能约束类的公有成员
- 接口不能约束构造函数
后续泛型还会在看到,ts的灵活性