关于接口,你可以理解为定义了属性和类型,但是没有定义其它任何东西的构造函数。接口可以用来约束对象,函数乃至代码结构。
如果你和后端同事正在联调,讨论好的数据类型是这样的:
[{id:1,name:'djtao'},{id:2,name:'dangjingtao'}...]
试问,如何规范这样的一个接口呢?
interface List {
id: number
name: string
}
interface Result {
data: List[]
}
const render = (result: Result) => {
result.data.forEach((item) => {
console.log(item)
})
}
const result = {
data: [{ id: 1, name: "djtao" }, { id: 2, name: "dangjingtao" }],
}
render(result)
这里我们使用接口来描述一个拥有id和name字段的List对象。并且定义Result是由List组成的数组。那么,一个标准的接口就被定义出来了。
如果后端给你的数据加了一个age字段:
const result = {
data: [{ id: 1, name: "djtao" }, { id: 2, name: "dangjingtao",age:31 }],
}
代码也不会报错。从这里体现了动态语言的特征(鸭子模型),只要满足接口的必要条件,就可以允许。
但是,如果:
render({
data: [{ id: 1, name: "djtao" }, { id: 2, name: "dangjingtao", age: 31 }],
})
这样又会报错。
这时候就需要类型断言。断言时,render的入参用Result类型s规范。如下两种都是等价的:
// 在react(tsx)中使用容易被混淆
render(<Result>{
data: [{ id: 1, name: "djtao" }, { id: 2, name: "dangjingtao", age: 31 }],
});
// tsx只能使用这种方式
render({
data: [{ id: 1, name: "djtao" }, { id: 2, name: "dangjingtao", age: 31 }],
} as Result)
你也可以使用字符串索引签名,在定义List时:
interface List {
id: number
name: string
[prop: string]: any // 允许接受其它属性
}
假设后端接口可能存在age属性,也可能不存在,前端需要根据此属性进行渲染——
interface List {
id: number
name: string
}
interface Result {
data: List[]
}
const render = (result: Result) => {
result.data.forEach((item) => {
console.log(item)
if (item.age) {// 报错
console.log(item.age)
}
})
}
const result = {
data: [{ id: 1, name: "djtao" }, { id: 2, name: "dangjingtao", age: 31 }],
}
render(result)
如果我给List定义了age,也不行(类型不兼容),这时候可以使用可选属性(加?)
interface List {
id: number
name: string
age?: number
}
这样就符合预期了。
因为id是重要属性,还可以对id设置只读属性(readonly)
interface List {
readonly id: number
name: string
age?: number
}
这时如果尝试修改id,就会报错。
上面的例子,属性个数都是确定的。如果是不确定的,可以使用索引属性。
比如:
interface StringArray {
[index: number]: string
}
let chars: StringArray = ["a", "b"]
表示声明了一个由字符串组成的数组。也可以混用:
interface StringArray {
[index: number]: string
[z: string]: string
}
回顾之前所学:在ts中定义一个函数可以是这样:
const add = (a: number, b: number) => a + b
用接口描述:一个函数,两个入参都是数字,返回的也是数字。
interface Add {
(x: number, y: number): number
}
// 或者
type Add = (x: number, y: number) => number
let add: Add = (a, b) => a + b
type和interface 多数情况下有相同的功能,就是定义类型。但有一些小区别:type:不是创建新的类型,只是为一个给定的类型起一个名字。type还可以进行联合、交叉等操作,引用起来更简洁。interface:创建新的类型,接口之间还可以继承、声明合并。如果可能,建议优先使用 interface。
接口也可以同时用做返回函数/字符串和数字:
interface Lib {
(): void
version: string
do(): void
}
// 这样实际上声明的是一个单例
// let lib: Lib = (() => {}) as Lib
// 考虑工厂模式 可以创建多个实例
const getLib = () => {
let lib: Lib = (() => {}) as Lib
lib.version = "1.0"
lib.do = () => {}
return lib
}