接口与类型别名:interface定义对象结构、type定义联合类型、接口继承、类型合并
好,咱们今天聊点实在的。TypeScript 里最常用的两个工具——interface 和 type,到底怎么选?怎么用?我见过不少项目,代码里混用一通,最后维护起来那叫一个头疼。今天我把我的经验掰开揉碎讲给你听。
interface:定义对象结构的首选
我个人习惯,只要定义对象的结构,优先用 interface。为什么?因为它更直观,而且 Vue 的生态对它支持更好。
// 定义一个用户对象
interface User {
id: number
name: string
email: string
age?: number // 可选属性
readonly createdAt: Date // 只读属性
}
const user: User = {
id: 1,
name: '张三',
email: 'zhangsan@example.com',
createdAt: new Date()
}
这里有个小细节——readonly。我在项目中遇到过,有人把 createdAt 写成普通属性,结果后面不小心改了它,导致数据不一致。嗯,这种坑踩过一次就记住了。
type:联合类型和交叉类型的利器
那 type 什么时候用?说白了,当你需要定义联合类型、交叉类型,或者一些复杂的类型组合时,type 才是主角。
// 联合类型:一个值可以是多种类型之一
type Status = 'active' | 'inactive' | 'pending'
type ID = string | number
// 交叉类型:合并多个类型
type AdminUser = User & { role: 'admin'; permissions: string[] }
// 类型别名也可以定义对象,但我不推荐
type Product = {
id: number
name: string
price: number
}
你想想看,如果不用联合类型,你可能会写一堆 if-else 判断,还容易漏掉某个分支。我曾经在一个支付系统里,用联合类型定义了订单状态,编译时就能发现所有未处理的状态分支——这种安全感,写代码的人最懂。
接口继承:复用与扩展
接口继承是 TypeScript 里非常实用的特性。它让你能基于已有接口扩展新接口,避免重复定义。
interface BaseEntity {
id: number
createdAt: Date
updatedAt: Date
}
interface User extends BaseEntity {
name: string
email: string
}
interface Post extends BaseEntity {
title: string
content: string
author: User
}
// 也可以继承多个接口
interface Comment extends BaseEntity, HasAuthor {
text: string
}
我在重构一个后台管理系统时,发现所有数据模型都有 id、createdAt、updatedAt 这三个字段。一开始每个接口都写一遍,后来抽成 BaseEntity,代码量直接减半。嗯,这就是继承的魅力。
类型合并:interface 的独特能力
这一点很多人不知道——interface 可以声明合并,而 type 不行。什么意思?就是同一个名字的 interface 可以定义多次,TypeScript 会自动合并它们。
// 第一次定义
interface User {
name: string
}
// 第二次定义,自动合并
interface User {
age: number
}
// 最终 User 包含 name 和 age
const user: User = {
name: '李四',
age: 25
}
这个特性在扩展第三方库的类型时特别有用。比如你想给 Vue 的 ComponentCustomProperties 添加自定义属性,就可以用声明合并。
// 在 .d.ts 文件中
import 'vue'
declare module 'vue' {
interface ComponentCustomProperties {
$api: typeof api
$t: typeof translate
}
}
我曾经在一个大型项目中,需要给 Vue 实例挂载全局方法。如果没有声明合并,就得用 any 绕过类型检查——那滋味,谁用谁知道。
如何选择:interface vs type
我总结了一套选择规则,你可以直接拿来用:
| 场景 | 推荐用 | 原因 |
|---|---|---|
| 定义对象结构 | interface | 更直观,支持声明合并,Vue 生态友好 |
| 定义联合类型 | type | type 天然支持联合类型 |
| 定义交叉类型 | type | type 用 & 符号更简洁 |
| 需要声明合并 | interface | type 不支持重复定义 |
| 定义函数类型 | type | type 定义函数签名更简洁 |
| 定义元组类型 | type | type 支持元组语法 |
实战:在 Vue 组件中应用
来看一个完整的例子,把今天讲的知识点串起来:
// 1. 定义基础类型
type Status = 'draft' | 'published' | 'archived'
// 2. 定义对象接口
interface Article {
id: number
title: string
content: string
status: Status
tags: string[]
publishedAt?: Date
}
// 3. 接口继承
interface FeaturedArticle extends Article {
coverImage: string
summary: string
}
// 4. 在组件中使用
import { defineComponent, PropType } from 'vue'
export default defineComponent({
props: {
article: {
type: Object as PropType<Article>,
required: true
},
status: {
type: String as PropType<Status>,
default: 'draft'
}
},
setup(props) {
// 这里 props.article 有完整的类型提示
// props.status 只能是 'draft' | 'published' | 'archived'
return {
displayTitle: computed(() => {
if (props.article.status === 'published') {
return props.article.title
}
return `[草稿] ${props.article.title}`
})
}
}
})
你看,这样写下来,整个组件的类型是自文档化的。新同事接手代码,看一眼类型定义就知道数据长什么样,能做什么操作。我曾经在一个没有类型定义的项目里,为了搞清楚一个对象的结构,翻了五个文件——那种痛苦,经历过一次就再也不想经历了。
好了,这一章的内容就到这里。下一章我们聊聊泛型——那个让 TypeScript 真正强大的特性。到时候见。