一、类型系统的由来:为什么我们需要它?
在我们日常编写的 JavaScript 代码中,变量可以随时被赋值为任何类型的值。这在小型项目中非常灵活,但随着项目体量增大,这种"动态"特性常常让我们陷入困境。比如,当你调用一个函数时,不确定它返回的到底是数组还是对象,于是你不得不在代码中写很多 typeof、instanceof 来防御。
TypeScript 的出现,就是为 JavaScript 添加了一套 类型系统。它让我们在编写代码时,就能明确变量、函数参数、对象结构应当是什么"形状",从而在代码运行之前就发现潜在的错误。
二、初识 interface 与 type
在 TypeScript 中,interface 和 type 是两种最常用的定义"形状"的方式。你可以把它们理解为一套"模具",用来约束数据的样子。
我们先看一段简单的代码:
typescript
typescript
// 使用 interface 定义一个用户结构
interface User {
name: string;
age: number;
avatarUrl: string;
}
// 使用 type 定义一个相同的用户结构
type UserType = {
name: string;
age: number;
avatarUrl: string;
};
// 这两个定义在大部分场景下可以互换使用
const u1: User = {
name: '张三',
age: 18,
avatarUrl: 'https://example.com/avatar.jpg'
};
const u2: UserType = {
name: '李四',
age: 20,
avatarUrl: 'https://example.com/avatar.jpg'
};
在上面的例子中,interface 和 type 都成功描述了"包含 name、age、avatarUrl 的对象"。对于初学者来说,它们看起来几乎是相同的。但在实际工程中,它们各自有着独特的能力和适用场景。
三、核心区别之一:继承与拓展
在面向对象编程中,"继承"是一种常见的代码复用方式。在 TypeScript 中,interface 和 type 都支持某种形式的拓展,但实现机制不同。
interface 使用 extends 关键字
typescript
php
interface Person {
name: string;
}
// Employee 通过 extends 继承了 Person 的所有属性
interface Employee extends Person {
job: string;
}
const e1: Employee = {
name: '小鹿',
job: '字节Agent开发工程师'
};
type 使用交叉类型 & 操作符
typescript
ini
type PersonType = {
name: string;
};
// EmployeeType 通过 & 将多个类型合并为一个
type EmployeeType = PersonType & {
job: string;
};
const e2: EmployeeType = {
name: '小猫',
job: '大厂苗子'
};
从使用习惯来看,interface 的 extends 语法更接近传统面向对象语言(如 Java、C#),而 type 的 & 符号则更偏向函数式编程中的组合思想。
四、核心区别之二:声明合并
这是 interface 一个比较特别的能力。如果你多次定义同名的 interface,TypeScript 会自动将它们合并为一个。
typescript
kotlin
interface Animal {
name: string;
}
// 同名的 interface 再次出现,新增一个 age 属性
interface Animal {
age: number;
}
// 此时 Animal 实际上等价于 { name: string; age: number; }
const dog: Animal = {
name: '三寸钉',
age: 2
};
这个特性被称为 声明合并 ,在需要拓展第三方库的类型定义时非常有用。例如,当你给 window 对象挂载自定义属性时,就可以通过多次声明 interface Window 来添加。
但是,type 不具备这个能力。如果你尝试重复声明同名的 type,TypeScript 会直接报错。
typescript
ini
type AnimalType = {
name: string;
};
// 这里会报错:标识符"AnimalType"重复
type AnimalType = {
age: number;
};
五、核心区别之三:能否表示非对象类型
interface 的设计初衷是描述对象的结构,虽然它也可以通过一些技巧描述函数类型,但整体上更偏向于对象。
而 type 的定位是"类型别名",它可以为任何类型起一个名字,包括原始类型、联合类型、元组等。
typescript
typescript
// 联合类型:ID 可以是 string 或 number
type ID = string | number;
// 元组类型:Point 必须是一个长度为2的数组,且第一个元素为 number,第二个也是 number
type Point = [number, number];
// 这些用法 interface 都无法做到
// 下面的写法是非法的
// interface ID = string | number;
在日常开发中,如果你需要定义一个变量可能是字符串也可能是数字,或者需要固定格式的数组(如坐标),type 会是更自然的选择。
六、核心区别之四:函数类型的定义
interface 和 type 都能描述函数类型。但写法上有细微差别。
使用 interface 描述函数类型
typescript
typescript
interface AddFn {
(a: number, b: number): number;
}
const add1: AddFn = (x, y) => x + y;
使用 type 描述函数类型
typescript
typescript
type AddType = (a: number, b: number) => number;
const add2: AddType = (x, y) => x + y;
对于大多数场景,两者效果一致。不过 type 的写法更简洁,更接近箭头函数的语法,因此在需要频繁定义函数类型的场景中,type 会更方便。
七、工程应用:React 组件中的类型约束
在 React 项目中使用 TypeScript 时,interface 和 type 都扮演着重要的角色。通常在定义组件的 Props(属性)时,我们会使用 interface,因为 Props 本质上是一个对象结构,且 interface 的声明合并特性在需要扩展第三方组件 Props 时非常方便。
下面是一个典型的 React 组件示例,它使用 interface 定义了组件的 Props 类型:
typescript
typescript
// 定义 User 的结构
interface User {
name: string;
age: number;
avatarUrl: string;
}
// 定义 UserCard 组件的 Props
interface UserCardProps {
user: User; // user 必须符合 User 接口
onEdit: (id: number) => void; // onEdit 是一个函数,接收 number 类型参数,无返回值
}
// React 组件定义,使用了泛型 React.FC<UserCardProps>
const UserCard: React.FC<UserCardProps> = ({ user, onEdit }) => {
// 组件内部的实现
return (
<div>
{/* 这里展示 user 的信息,并可以通过 onEdit 触发编辑操作 */}
</div>
);
};
export default UserCard;
在这个例子中,UserCardProps 明确了组件接收的属性类型,避免了在组件内部对 user 或 onEdit 的误操作,同时也为使用该组件的开发者提供了清晰的类型提示。
八、更进阶的工程化应用:Harness 流水线中的类型设计
聊完了基础的类型区别,我们来看一个实际工程中如何运用这些概念。这里引入一个概念------Harness 工程。
在大型语言模型(LLM)应用的开发中,我们常常会遇到一个痛点:模型生成的内容质量不稳定。同样的提问,模型可能给出完全不同的答案。为了解决这个问题,工程师们设计了一种叫做 Harness 的流水线框架。
Harness 的核心思想可以概括为三个步骤:
- 并行生成:让大模型针对同一个任务,生成多个候选结果(利用随机性覆盖更多可能性)。
- 自动评测:用大模型自身作为裁判(LLM as Judge),对每个候选结果打分。
- 择优选择:选出分数最高的结果作为最终输出。
在这个过程中,TypeScript 的类型系统可以帮助我们更安全地组织代码。例如,我们可以定义清晰的数据结构来约束候选代码、评分结果和最终输出。
以下是一个简化的 Harness 核心流程的类型定义示例:
typescript
typescript
// 定义候选代码的结构
interface CodeCandidate {
id: string; // 唯一标识
content: string; // 代码内容
}
// 定义评分结果的结构
interface ScoreResult {
candidateId: string;
score: number; // 0-10 分
reason?: string; // 可选的评分理由
}
// 定义最终优选结果
interface BestResult {
code: string;
score: number;
}
可以看到,通过 interface,我们为流水线中的每一个环节的数据都设置了明确的约束。这使得后续的处理函数(如评分函数、选择函数)的输入和输出都非常清晰,大大降低了出错概率。
九、怎么选:interface 还是 type?
根据上面的分析,我们可以总结出一个相对实用的选择策略:
-
优先使用
interface:- 当你要定义一个对象的结构(比如 React 的 Props、State,或者 API 响应数据)。
- 当你需要利用声明合并特性来扩展类型(比如给全局对象添加属性)。
- 当你希望代码风格更接近传统的面向对象语言。
-
优先使用
type:- 当你要定义联合类型(
string | number)、元组类型([string, number])或原始类型的别名。 - 当你要利用交叉类型(
&)组合多个类型。 - 当你要定义复杂的工具类型(比如条件类型、映射类型)。
- 当你要定义联合类型(
这里需要特别指出的是,两者并非互斥 。在实际项目中,你完全可以混合使用 interface 和 type,根据具体的场景选择最合适的工具。
十、写在最后
TypeScript 的类型系统为我们提供了一种强大的方式来描述数据结构和函数签名。interface 和 type 作为其中最基础的两个构件,虽然乍看相似,但各自有着独特的设计哲学和适用场景。
interface更适合描述对象,支持继承和声明合并,是面向对象风格的好帮手。type更加通用,可以描述任何类型,结合联合类型、交叉类型等特性,在函数式编程和复杂类型操作中表现突出。
理解这些差异,并能在实际开发中做出合理的选择,是 TypeScript 进阶路上重要的一步。希望这篇文章能帮助你更清晰地认识它们,并在项目中更自信地使用它们。
如果你对 Harness 工程或 LLM 应用开发感兴趣,欢迎查阅我专栏中的其他文章,我们一起探索更多工程化落地的实践。