OveUI博客
老前端的戎码生涯

typescript学习之路(五) —— ts的接口

项目开发中,我们会遇到这样的需求。就是我们需要开发一个函数或者一个插件或者库等等,需要调用者调用时传入指定的参数,比如我们需要的是string类型的或者对象类型的,再或者是对象类型的且里面的属性必须包含什么属性等,如果说我们自己的约定属于彼此之间的约定,那么接口就是ts为你的代码定义的强制契约。

一般情况下的我们定义的约定

const printFullName = ({ firstName, lastNmae }) => return `${ firstName } ${ lastName }`

printFullName({ 
firstName: '爱新觉罗', 
lastName: '小胖纸'
}) 
// 输出 爱新觉罗 小胖纸

我们期望的是这样,但是呢?一句话怎么说来着,你永远不要去揣测用户的心里。
可能他们调用的时候传的是这个

printFullName({ 
firstName: '爱新觉罗', 
lastName: 18
})

这样传参是合法的,但是却不是我们想要的,而结果当然也是不可预期的。

接口初探

那么接下来我们看接口是如何进行约束的
ts规定了使用interface声明一个接口

interface FullName {
firstName: string
lastName: string
}
// 接着我们改造下上面的函数定义
const printFullName = ({ firstName, lastNmae }: FullName ): string => return `${ firstName } ${ lastName }`

这样我们就给函数的参数进行了一层约束,约束规则如下

必须是一个对象
对象必须包含firstName和lastName属性
firstName和lastName的属性值必须是string类型
// 我们试着调用下
printFullName({ 
firstName: '爱新觉罗', 
lastName: '小胖纸'
}) // success 爱新觉罗 小胖纸
printFullName({ 
firstName: '爱新觉罗', 
lastName: 18
}) // error lastname不能将number类型分配给string

这样我们就约束了调用者的传参,其结果也就符合预期了

可选属性

还是拿上面的例子举例,可选属性就是在对象的后面添加?

// 定义一个接口,firstName 必须有,lastName和age可有可无
interface FullName {
firstName: string
lastName?: string
age?: number
}
const printFullName = ({ firstName, lastName = '', age }: FullName): string => {
return `${ firstName } ${ lastName } ${ age }`
}
printFullName({ firstName: '爱新觉罗' } // 爱新觉罗
printFullName({ firstName: '爱新觉罗', age: 22 } // 爱新觉罗 22
printFullName({ firstName: '爱新觉罗', lastName: '小胖纸', age: 22 } // 爱新觉罗 小胖纸 22

这样我们在传参时,就可以根据条件来进行对应传参

只读属性

顾名思义,只读属性规定了对象只能在初始化时对其进行赋值,一旦初始化完毕,就修改不了

// 定义一个接口,firstName 
interface FullName {
firstName: string
readonly lastName: string
}
const fullName: FullName = { firstName: '爱新觉罗', lastName: '小胖纸' }
fullName.lastName // 小胖纸
// 当我们尝试着修改时
fullName.lastName = '大胖纸' // error lastName is read-only

同样的数组也是对象的一种,在重温下数组的定义

const arr: number[] = [1, 2, 3]
// 或
const arr: Array<number> = [1, 2, 3]

因此对于数组而言也有只读属性

const arr: ReadonlyArray<number> = [1, 2, 3]

arr[1] // 2
// 当我们尝试着去修改值时
arr[1] = 1 // error arr is read-only

当然你可以使用类型断言重写arr,但是既然设置了只读,就不应该再去修改它,这里只做说明,不推荐

额外的属性检查

对于刚刚我们定义的对象不知道有没有小伙伴觉得疑惑,为什么,接口定义了几个属性,参数就传递了几个或者创建的对象就只有那么几个属性。这里集中说明下

// 这里我们定义一个接口
interface FullName {
firstName: string
lastName: string
}
// 当我们使用接口时
const fullName: FullName = { firstName: '爱新觉罗', lastName: '小胖纸', age: 22 }
// error 因为FullName没有包含age属性

尴尬了,难道就只能传这两个嘛,有时候赋值一个对象,里面可多属性的怎么破

使用类型断言
我们把赋值的对象断言成符合要求的FullName接口类型

const fullName: FullName = { firstName: '爱新觉罗', lastName: '小胖纸', age: 22 } as FullName

这样赋值的类型就满足要求了

使用类型兼容

举个例子,假设我有500平房子,500万车子,1000万存款(开玩笑哈),你想和我对等换,那么你是不是也得有房子,车子和存款是吧,当然你要是多加点股票啥的我更乐意,这就说明了类型兼容,你要赋值给我,那么你的属性除了和我一样之外,还必须比我多,那么类型兼容就是这样
让我们使用类型兼容试试

// 这个就是你有的 而接口就是我有的,你比我的多
const fullNameAll = { firstName: '爱新觉罗', lastName: '小胖纸', age: 22 }
const fullName: FullName = fullNameAll

这样就行了,绕过了接口的额外属性检查

使用索引签名

索引签名是个啥,先理解索引,对象的索引是key,数组的索引是下标,那么索引签名呢?就是可以通过索引获取到值,让我们用例子看,还是刚才的例子

// 这里我们定义一个接口
interface FullName {
firstName: string
lastName: string
[ key: string ]: number
}

这里我们为interface接口加了索引签名,意思是什么呢,就是我们通过string类型去索引FullName,可以获取到number类型的返回值,这样的想法是正确的,但运行之后我们会发现,竟然报错了,原来索引签名的返回值类型必须是已经定义的父类型。翻译过来的大白话就是,[ key: string ]: number这个东西就是个可变类型,可以匹配任意多个属性是age: 22这样的属性,而我们已经定义好的属性确是firstName: string返回值类型不匹配,所以报错,那么如何解决呢?
第一就是保持类型统一,这个限制很大,不可能刚刚好都是一个类型。
第二就是把索引签名的返回值类型设为any。这样就可以了

interface FullName {
firstName: string
lastName: string
[ key: string ]: string
}
// 或
interface FullName {
firstName: string
lastName: string
[ key: string ]: any
}

当然这是对象的索引签名,还有数字的索引签名,碧如

interface StrArr {
[ index: number ]: string
}

const strArr: StrArr = ['小胖纸', '大胖纸']

还有一个需要主要的点就是使用数字索引时,会先转换成string类型再去索引

在这里插入图片描述

 

这里我们看到string类型覆盖掉了number类型的索引
当然,索引签名也是可以使用只读属性的,用法和上面一样

接口的继承

接口是可以继承的,想类那样使用extends关键字就可以

interface FullName {
firstName: string
lastName: string
}
interface FullInfo extends FullName {
age: number
}
const fullInfo: FullInfo = {
firstName: '爱新觉罗',
lastName: '小胖纸',
age: 22
}

缺少任意一项就会出现额外类型检查的错误

接口对函数的约束

函数式js的一等公民,它也是对象,因此,接口对函数也是起作用的,让我们来看个例子

interface PrintFullName {
(firstName: string, lastName: string): string
}
const printFullName: PrintFullName = (firstName, lastName): string => `${ firstName } ${ lastName }`
printFullName('爱新觉罗', '小胖纸') // 爱新觉罗 小胖纸

这里我们使用(firstName: string, lastName: string) => string来对函数的传入参数和返回值类型进行约束,传入参数是string类型,返回值也是string类型
另外需要注意的是,函数类型的接口,只检查对应参数的值类型,而不需要值相等

const printFullName: PrintFullName = (str1, str2): string => `${ firstName } ${ lastName }`

你也可以给参数指定类型和函数的返回值不指定类型

const printFullName: PrintFullName = (str1: string, str2: string) => `${ firstName } ${ lastName }`

一般而言,函数类型在定义函数时我们是不需要指定返回值类型的,ts解析器能够推断出来
还有对于函数类型的接口约束推荐使用type类型别名来定义,上面的列子我们在改写下

type PrintFullName = (firstName: string, lastName: string) => string

接口的混和类型

对于对象而言,我们可以定义属性,那么我们是不是也可以定义方法,答案当然是阔以了。比如

// 定义一个对象,里面包含属性和方法
interface PrintInfo {
userName: string
(age: number): void
print(): void
}

const printInfo = (): PrintInfo => {
const p = (age: number) => { 
console.log(`${ age }`)
}
p.userName= '小胖纸' // 主要这里不能是name 设为name则是想改掉函数本身自带的name属性
p.print = () => {}
return p
}
let p = printInfo()
p(22) // 22
p.userName // 小胖纸
p.print() //

// 或者这么写
const printInfo = (): PrintInfo => {
let p = <PrintInfo >((age: number) => { 
console.log(`${ age }`)
})
p.userName= '小胖纸' // 主要这里不能是name 设为name则是想改掉函数本身自带的name属性
p.print = () => {}
return p
}

类可以实现接口

如果你希望在类中使用必须要被遵循的接口(类)或别人定义的对象结构,可以使用 implements 关键字来确保其兼容性

interface FullName {
firstName: string
lastName: string
}

class Name implements FullName {
firstName: string
lastName: string
}

赞(0)
未经允许不得转载:UI树欲静 » typescript学习之路(五) —— ts的接口