在使用 JavaScript 开发时,对象(Object)是我们每天打交道最多的数据结构之一。JavaScript 中的对象非常自由,我们可以随时往一个对象添加属性或删除属性。
但是,这种 “极度自由” 在大型企业级项目中往往是致命的。假设我们从后端接口拿到了一个包含用户信息的对象,然后在代码中不小心把 userName 写成了 username,或者漏传了某个必填字段,此时 JavaScript 是不会在编写阶段给你任何警告的,它会默默吞下错误,直到程序运行崩溃。
为了对 “对象” 这种数据结构进行规范约束,使得错误在代码编写阶段就能暴露出来,TypeScript 引入了一个非常重要的概念:接口(interface)。
TypeScript 接口是什么?
在 TypeScript 中,接口(interface)本质上是一份 “契约”。它是专门用来约束对象应该长什么样,官方称之为对象的 “形状(Shape)”。
只要一个对象声明了遵守某个接口,那么该对象就必须严格遵守 “契约” 上的规定:即不能多一个属性,也不能少一个属性,并且每个属性的数据类型都要一一对应。
语法:
interface 接口名 {
属性名1: 类型1;
属性名2: 类型2;
...
}说明:
需要注意的是,在 TypeScript 的规范中,接口名的首字母一般推荐使用大写(即大驼峰命名法),比如 User、Product 等。
示例 1:定义接口
// 定义接口
interface User {
name: string;
age: number;
}
// 使用接口(用于创建对象)
const currentUser: User = {
name: "Jack",
age: 20
};
console.log(currentUser.name);运行结果如下。
Jack分析:
在这个例子中,我们定义了一个 User 接口,它规定了对象必须包含 name(字符串)和 age(数字)。
接口就像是一份 “契约”,如果我们试图挑战这份契约,比如少写一个 age,或者多写一个 gender,那么 TypeScript 编译器立马就会 “翻脸” 飘红报错,比如:
// 定义接口
interface User {
name: string;
age: number;
}
// 错误写法 1:少了 age 属性
const user1: User = {
name: "Jack"
};
// 报错:类型 "{ name: string; }" 中缺少属性 "age"
// 错误写法 2:多了 gender 属性
const user2: User = {
name: "Lucy",
age: 18,
gender: "女"
};
// 报错:对象文字可以只指定已知属性,并且 “gender” 不在类型 “User” 中TypeScript 接口的 “可选属性”
在真实的业务开发中,对象的属性很多时候并不都是必填的。比如在用户注册时,用户的 “昵称” 和 “密码” 是必填的,但像 “兴趣爱好(hobby)” 这种就是选填的。
为了应对这种场景,TypeScript 接口提供了 “可选属性” 的语法。
语法:
interface 接口名 {
属性名1 ?: 类型1;
属性名2 ?: 类型2;
...
}说明:
我们只需要在属性名的后面加上一个问号 “?” 即可,该属性就会变成 “可选属性”。
示例 2:使用可选属性
interface User {
name: string;
age: number;
hobby?: string; // 这是一个可选属性
}
// 包含可选属性
const user1: User = {
name: "Jack",
age: 20,
hobby: "Coding"
};
// 不包含可选属性,同样完全合法
const user2: User = {
name: "Jack",
age: 25
};
console.log(user1.hobby);
console.log(user2.hobby);运行结果如下。
Coding
undefined分析:
可选属性的好处在于:它既对有可能存在的属性进行了类型限制,又赋予了对象一定的灵活性。
对于这个例子来说,如果 user2 没写 hobby,此时它是合法的;但如果 user2 写了 hobby,那么它的值就必须是 string 类型,我们绝对不能塞个数字进去。
TypeScript 接口的 “只读属性”
有些对象的属性一旦被创建,在它整个生命周期内都不允许被修改。最典型的例子就是数据库中的自增 id,或者是用户的身份证号。
在 TypeScript 接口中,我们可以在属性名前面加上 readonly 关键字,将其标记为只读属性。
语法:
interface 接口名 {
readonly 属性名1: 类型1;
readonly属性名2: 类型2;
...
}说明:
readonly 是放在属性名的 “前面”,而可选属性符号 “?” 是放在属性名 “后面”。
示例 3:使用只读属性
interface Product {
readonly id: number; // 只读属性
title: string;
price: number;
}
const myPhone: Product = {
id: 1001,
title: "智能手机",
price: 4999
};
// 正常操作:修改普通属性
myPhone.price = 3999;
// 错误操作:试图修改只读属性
myPhone.id = 1002;
// 报错:无法为 “id” 赋值,因为它是只读属性const 与 readonly 的区别
初学的小伙伴很容易把 readonly 和 const 搞混,我们只需要记住一个口诀就可以了:变量用 const,属性用 readonly。
const:用来限制整个变量的内存地址不能改变。readonly:是在接口中使用的,专门用来限制对象内部的某个具体属性不能被重新赋值。
TypeScript 接口的 “任意属性(索引签名)”
在实际开发中,偶尔会遇到一种非常动态的数据结构:我们只知道这个对象里全都是某种类型的值,但事先并不知道具体的属性名(key)会叫什么。
比如,后端返回了一个记录页面访问量的配置对象,里面的 key 是动态的页面路径,value 是具体的访问数字。对于这种 “未知名称” 的属性,我们需要使用 TypeScript 的 “索引签名(Index Signatures)”。
语法:
interface 接口名 {
[key: string]: 类型;
}示例 4:使用任意属性
interface PageViews {
// 必须包含一个总数
total: number;
// 允许有任意数量的、名字是字符串、值是数字的额外属性
[key: string]: number;
}
const views: PageViews = {
total: 1005,
"/home": 500,
"/about": 300,
"/contact": 205
};
console.log(views["/home"]);运行结果如下。
500分析:
在这个例子中,[key: string]: number 用于告诉 TypeScript:除了上面明确规定的 total 之外,这个对象还可以接受无数个额外属性,只要属性名是字符串、值是数字即可。
TypeScript 接口的 “业务场景”
学到这里,小伙伴们肯定会问:“接口在实际项目中到底应该怎么用呢?”
实际上,接口最最经典的应用场景就是 “约束函数的参数” 和 “约束 API 请求的返回值”。当函数的参数是一个拥有众多属性的复杂对象时,使用接口能让代码变得极具可读性和安全性。
示例 5:使用接口约束 “函数参数”
// 定义文章接口
interface Article {
title: string;
author: string;
isPublished: boolean;
}
// 使用接口来约束函数参数
function printArticleInfo(article: Article) {
console.log(`《${article.title}》 - ${article.author}`);
}
const newArticle: Article = {
title: "TypeScript 接口",
author: "Jack",
isPublished: true
};
// 调用函数
printArticleInfo(newArticle);运行结果如下。
《TypeScript 接口》 - Jack分析:
在 Vue 或 React 的大型项目中,如果我们向某个组件传递复杂的 props,或者封装 Axios 的时候,绝大部分情况都是先抽离定义成一个 interface,然后再使用这个 interface 去约束数据。这也就是为什么大家常说:“面向接口编程” 是 TypeScript 的核心灵魂。
示例 6:使用接口约束 “API 返回值”
// 定义接口
interface ApiResponse {
code: number;
msg: string;
data: {
id: number;
username: string;
avatar?: string; // 头像是可选的
};
}
// 模拟从后端获取到的真实数据
const res: ApiResponse = {
code: 200,
msg: "获取用户信息成功",
data: {
id: 1001,
username: "Jack"
}
};
// 在页面中安全地使用这些数据
console.log(res.data.username);运行结果如下。
Jack分析:
在前后端分离的开发中,前端最常做的事情就是调用后端 API 获取数据。后端返回的数据往往是嵌套的复杂 JSON 对象。为了防止在页面渲染时因为 “字段名写错” 而导致白屏,我们通常会使用接口把返回的数据结构严格定义出来。
在这个例子中,如果没有使用 ApiResponse 这个接口来约束,当我们敲下 res.data. 的时候,编辑器是完全不知道里面有什么数据的。我们只能靠死记硬背或者去翻后端的 API 文档,如果一不小心把 username 写成了 userName,此时 Bug 就诞生了。
但是有了接口的约束,当我们在 VS Code 中敲下 res.data. 的那一刻,编辑器会自动弹出 id、username 和 avatar 的智能提示!这不仅极大地提高了我们的开发效率,还相当于给我们的代码上了一份 “保险”,让不可控的后端数据变得完全可控。
