接口定义对象类型
我们使用interface
关键字来定义接口。
interface User {
name: string
}
上面就定义了一个User
接口。
// 定义一个user对象,它的类型是User类型
const user: User = {name: 'randy'}
必选属性和必选方法
必选属性和必选方法就是我们接口定义的属性和方法,具体的对象必须有这些属性和方法,不然在编辑阶段会报错。
interface User {
name: string; // 必须属性
hi: (num: number) => number; // 必须函数,返回值为number
hi2: () => void; // 必须函数,没有返回值的方法
hi3(): void; // 必须函数,没有返回值的方法,简写形式
}
我们使用User
接口定义对象的时候就必须具有这四个属性。否则在编译阶段就会报错。
let user: User = {
name: 'randy',
hi: (num: number) => num,
hi2: () => {console.log('hi2')},
hi3: () => {console.log('hi3')}
}
那有时候我们的属性或方法不是确定的呢?那就需要用到可选属性和可选方法啦。
可选属性和可选方法
可选属性和可选方法我们只需要在定义属性的时候使用?
表示,跟前面函数章节说的可选参数是一样的。
interface User {
name?: 'randy',
hi?: (num: number) => number,
}
上面的例子,我们定义对象的时候就可以没有name
属性和hi
方法。
let user: User = {}
只读属性和只读方法
只读属性和只读方法就是我们定义的属性和方法是不能被修改的。我们使用readonly
关键字来定义只读属性和只读方法。
interface User {
readonly name: 'randy',
readonly hi: (num: number) => number,
}
只读属性和方法也是必选属性和必选方法,也就是定义对象的时候是必须定义的,不然会报错。
和必选属性和必选方法的区别就是,必选属性和必选方法定义完后可以重新赋值,但是只读属性和只读方法就不能再重新赋值了。
let user: User = {
name: 'randy',
hi: (num: number) => num,
}
user.name = 'demi' // error 无法分配,因为它是只读属性
user.hi = (num: number) => num + 1 // error 无法分配,因为它是只读属性
但是如果只是必选属性和必选方法我们是可以重新赋值的。
任意属性和任意方法
因为js
灵活的缘故,我们使用js的时候经常会在使用中途添加属性和方法,所以就有了不确定性。但是我们的ts
又是在使用前必须前明确定义属性和方法,这就产生了冲突,那我们想在使用中途添加属性和方法该怎么办呢?
这就需要用到索引签名啦。
interface User {
name: string;
[prop: string]: any;
}
这里我们使用[prop: string]: any
定义了一个索引签名。它表示可以接收任意的属性名,只要是字符串类型的就可以,接收任意的属性值。
let user: User = {name: 'randy', age: 24 }
// 还能动态添加任意属性和方法
user.count = 1
user.count2 = 12
user.say = () => {console.log("haha")}
// key只能是string,所以不能为symbol类型
user[Symbol(1)] = 123 // Error
有了索引签名,大大提高了我们的灵活性。
需要注意,索引签名参数类型必须是 “string”、“number”、“symbol”或模板文本类型必选属性和可选属性值的类型必须是任意属性值类型的子集
索引签名还可以有更多用法。比如我们有个user
对象,它的联系方式有很多种,不同的人有不同的情况。
// 张三
{
name: '张三',
concat: {
wechat: 'xiaozhang@163.com',
qq: '1845751425@qq.com',
// ...
}
}
// 李四
{
name: '李四',
concat: {
phone: '17673298765',
qq: '1845751425@qq.com',
// ...
}
}
我们发现,concat
对象的键始终是string
,值也始终是string
,这时我们就可以为concat
属性定义一个索引类型的接口。
interface Concat {
[prop: string]: string
}
这样我们的User
接口就可以使用Conca
t接口来定义我们的concat
属性啦。
interface User {
name: string;
concat: Concat
}
接口定义函数类型
我们使用接口除了可以定义对象类型外,还可以定义函数类型。
interface Say {
(str: string) : string
}
上面定义了一个接收一个string
参数并返回string
类型的函数。
const say: Say = (str) => str
接口定义构造函数
接口除了定义普通函数,还可以定义构造函数。
interface ToString {
new (): string;
}
declare const sometingToString: ToString;
new sometingToString(); // ok
接口的继承
接口是可以继承的,学过java
的同学肯定清楚。
interface User1 {
name: string
}
interface User2 extends User1 {
age: number
}
// 这样User2类型就既有了 name 属性又有了 age 属性
const user: User2 = {name: 'randy', age: 24}
并且接口是支持多继承的。
interface User3 extends User1, User2 {
sex: string
}
这样我们的接口User3
就同时具备了name、age、sex
。
接口和类型别名type的区别
说到这里很多小伙伴们肯定会有疑惑了,为什么有了类型别名还要有接口呢?不都是用来定义类型吗?
这里笔者说说自己的见解。
接口能实现继承
虽然接口和type
都能定义类型,但是接口可以使用继承,相当于多个接口组合,让我们的代码更具灵活性。但是type
是不支持的。
interface User3 extends User1, User2 {
sex: string
}
接口能多次定义
interface User2 {
name: string;
}
interface User2 {
age: number;
}
// User2 同时具备name和age属性
const user2: User2 = { name: "randy", age: 24 };
// 会报错 type不能重复定义
// type User2 = { name: string };
// type User2 = { age: number };
接口能被实现
接口能被class
来实现。type
是不支持的。
class People implements User2 {
name = "randy";
age = 24;
}
关于实现,笔者再后面讲述class
的时候会细说。
类型别名支持给原始类型取别名
这句话什么意思呢?笔者举个简单的例子就知道了。
// 给string原始类型取别名
type newstring = string;
const str2: newstring = "randy";
const str3: string = "randy";
console.log(str2, str3);
上面的例子,我们用给string
类型去了别名 newstring
。虽然type支持这样做但是我们平时开发中基本上不会这么去用。
大多数是在联合类型的时候使用type
去重新定一下。
type NumAndStr = number | string;
总体来说,笔者觉得简单场景我们就使用type
,复杂场景咱们就选择接口。
系列文章
TypeScript入门之类型推断、类型断言、双重断言、非空断言、确定赋值断言、类型守卫、类型别名
后记
感谢小伙伴们的耐心观看,本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力!