在上一节中,我们学习了如何使用 “接口(interface)” 来约束对象的形状。TypeScript 的接口非常强大,但它有一个局限性:只能用来定义 “对象”。
在实际开发中,有时我们不仅仅需要给对象约束形状,还需要给普通的基础类型(比如字符串、数字)、甚至复杂的组合类型起一个具有语义化的名字。此时,就需要用到 TypeScript 的 “类型别名(type)”了。
TypeScript 类型别名是什么?
TypeScript 类型别名,顾名思义就是:给一个 “已经存在的类型” 起一个 “小名”(或者叫别名)。
语法:
type 别名 = 具体类型;说明:
类型别名的主要作用是为了让代码更加简洁、可读性更强。就像在日常生活中,一个人可能有大名,也有小名,但它们指代的都是同一个人。
示例 1:给基础类型起别名
// 定义别名
type Name = string;
type Age = number;
// 使用别名定义变量
const userName: Name = "Jack";
const userAge: Age = 20;
console.log(userName);
console.log(userAge);运行结果如下。
Jack
20分析:
在这个例子中,我们给 string 类型起了个别名叫 “Name”,并且给 number 类型起了个别名叫 “Age”。也就是说,Name 和 string 指的是同一个类型,而 Age 和 number 指的也是同一个类型。
虽然看起来像是脱裤子放屁,但在某些十分复杂的泛型推导场景中,给基础类型起别名能够让代码语义更清晰。
示例 2:给数组类型起别名
type numArr = number[];
type strArr = string[];
const prices: numArr = [10, 20, 30];
const colors: strArr= ["red", "green", "blue"];
console.log(prices[0]);
console.log(colors[1]);运行结果如下。
10
green分析:
除了基础类型之外,我们还可以使用 type 给数组、对象、接口等起一个别名。
TypeScript 使用 “类型别名” 定义对象
在 TypeScript 中,虽然接口(interface)可以用来定义对象,但类型别名(type)同样也能做到,而且两者语法极其相似。
示例 3:type 定义对象
type User = {
name: string;
age: number;
hobby?: string; // 同样支持可选属性
};
const currentUser: User = {
name: "Jack",
age: 20
};
console.log(currentUser.name);运行结果如下。
Jack分析:
type 定义对象,和interface 定义对象,两者使用起来可以说是一模一样。
TypeScript 类型别名的用途
这里小伙伴们肯定会问:“既然已经可以使用 interface 来表示对象,那 TypeScript 为什么还要多搞一个 type 出来呢?”
这是因为 interface 只能用于表示对象,而 type 可以用于表示任何类型。
1. 定义元组类型
我们前面已经学习过元组了,元组的类型定义通常是一长串的方括号。如果到处写,代码可读性性会非常差。此时我们可以用 type 给它封装一下。
示例 4:type 定义元组类型
// 为元组起个别名
type Point = [number, number];
// 复用别名
const startPoint: Point = [10, 20];
const endPoint: Point = [100, 200];
console.log(startPoint[0]);运行结果如下。
10分析:
我们为 “[number, number]” 起了一个别名 “Point”,后面就不用每次都写 “[number, number]”,而是直接使用 “Point” 就可以了。这样的代码更加优雅,复用性也更强。
2. 定义联合类型(最常用)
在真实的业务开发中,某个变量的值可能只能是固定的几个字符串之一。比如订单状态,只能是 "pending"、"success" 或 "failed"。这个时候,Type 就派上大用场了。
示例 5:type 定义联合类型
// 定义一个联合类型
type OrderStatus = "pending" | "success" | "failed";
// 正常赋值
const currentStatus: OrderStatus = "success";
console.log(currentStatus);
// 错误赋值:如果拼写错误或者赋了不在约定里的值,直接报错
// const wrongStatus: OrderStatus = "loading";
// 报错:不能将类型“"loading"”分配给类型“OrderStatus”。运行结果如下。
success分析:
像这个例子的用法,在企业级项目中非常普遍。通过 type 配合联合类型(多个类型用 “|” 隔开),我们直接在类型层面锁死了变量的取值范围,甚至连 if...else 校验都省了。只要你赋的值不对,TypeScript 就会立马报错拦截。
实际上,无论是基础类型、对象、联合类型还是元组,我们都可以用 type 来封装并起个直观的名字。它极大程度地提升了代码的复用性和可读性。
3. 定义函数类型
在组件化开发中,我们经常需要把一个函数当作参数传来传去(比如组件的各种回调事件)。如果每次都把又长又臭的函数参数和返回值写在组件的接收处,代码会变得非常臃肿。
此时,我们可以使用 type 来给函数结构起个别名,这也是最优雅的做法。
示例 6:type 定义函数类型
// 定义一个函数类型:接收两个数字,返回一个数字
type CalculateFn = (a: number, b: number) => number;
// 使用别名来约束具体的函数
const add: CalculateFn = (x, y) => {
return x + y;
};
const multiply: CalculateFn = (x, y) => {
return x * y;
};
console.log(add(10, 20));
console.log(multiply(10, 20));运行结果如下。
30
200分析:
在这个例子中,我们使用 type 提取了一个通用的函数结构 CalculateFn。后面无论是写加法还是乘法,只要直接套用这个别名,编辑器就会自动对 x 和 y 进行数字类型的推导。
那么 type 和 interface 到底怎么选?
既然 type 和 interface 都可以用来定义对象,那我们在项目开发中,到底应该用哪一个呢?
它们俩虽然非常相似,但有一个非常核心的底层区别:interface 支持同名合并,而 type 不支持。
示例 7:接口的同名合并
// 第 1 次定义 User 接口
interface User {
name: string;
}
// 第 2 次定义同名的 User 接口
interface User {
age: number;
}
// TypeScript 会自动把它们合并成一个完整的 User
const currentUser: User = {
name: "Jack",
age: 20
};
console.log(currentUser);运行结果如下。
{ name: 'Jack', age: 20 }分析:
在这个例子中,如果我们使用 type 去重复定义同名的类型,此时TypeScript 会直接报错:
报错:标识符“User”重复在实际开发中,对于 interface 和 type,我们应该遵循以下最佳实践:
- 优先使用 interface 定义对象:如果你是在定义普通的业务数据结构(比如后端的 API 返回值、Vue/React 组件的 props 对象),并且希望它具备良好的扩展性,应该优先使用 interface。这也是 TypeScript 官方强烈推荐的做法。
- 只在特定场景使用 type:当你需要给基础类型起别名,或者你需要用到联合类型(如 "success" | "failed")、元组类型时,不要犹豫,直接上 type。
一句话总结就是:对象和类的结构约束,交给 interface;灵活多变的类型组合与别名,交给 type。
