首页 / TypeScript 入门教程 / 接口(interface)详解

TypeScript 入门教程

接口(interface)详解

本教程共 80 篇 · 第 24 篇 · 更新于 2026-08-10 · 约 10 分钟阅读

TypeScriptTypeScript 入门教程接口interface

本节目标:掌握 interface 的完整语法,能用它定义对象的形状、函数的签名和动态键值对的类型。

上一章我们在函数参数里直接写了 { x: number; y: number } 这样的类型注解。这种写法用一两次还行,但如果同一个形状到处出现,每次都要重写一遍,又丑又容易出错。

这时候就该请出 TypeScript 专门为对象设计的一等公民——接口(interface)

接口的基本语法

接口用 interface 关键字定义,给对象形状起个名字:

interface Point {
  x: number;
  y: number;
}

let p1: Point = { x: 10, y: 20 };
let p2: Point = { x: 5, y: 0 };

给形状起了名字之后,到处都可以用 Point 而不用重复写类型。而且接口本质上就是一份”契约”——任何被标注为 Point 类型的对象,都必须提供 xy 这两个数字属性。

Note

接口只存在于编译时。编译成 JavaScript 后,接口会被完全抹掉,零运行时开销。

可选属性

跟对象字面量一样,接口里也可以用 ? 标记可选属性:

interface User {
  name: string;
  age?: number;
  email?: string;
}

const u1: User = { name: "Alice" };                              // ✅
const u2: User = { name: "Bob", age: 30 };                       // ✅
const u3: User = { name: "Carol", age: 25, email: "c@x.com" };   // ✅

在 strict 模式下(TypeScript 7.0 默认),age? 的类型是 number | undefined。这意味着你读取 u1.age 时,TypeScript 会强制你处理 undefined 的情况:

// ❌ 不能直接用,可能是 undefined
// console.log(u1.age.toFixed(2));

// ✅ 先检查
if (u1.age !== undefined) {
  console.log(u1.age.toFixed(2));
}

// ✅ 或者用可选链(后面章节会详细讲)
console.log(u1.age?.toFixed(2));

只读属性

有些属性创建之后就不该被改了。用 readonly 标记:

interface Config {
  readonly apiUrl: string;
  readonly timeout: number;
  retryCount: number;
}

const config: Config = {
  apiUrl: "https://api.example.com",
  timeout: 5000,
  retryCount: 3,
};

config.retryCount = 5;     // ✅ 普通属性可以改
// config.apiUrl = "...";   // ❌ 只读属性不能改
Tip

readonly 是编译时约束,运行时没有任何保护。如果你的代码被编译成 JS 后在不检查类型的环境跑,只读属性仍然可以被修改。需要真正的不可变数据请用 Object.freeze()

不过有个坑:readonly 只作用于第一层。如果属性本身是个对象,对象内部的属性仍然可以改——这个问题我们在第 26 章细聊。

函数类型接口

接口不仅能描述”有属性的对象”,还能描述”函数本身”:

interface GreetFn {
  (name: string): string;
}

const greet: GreetFn = (name) => `你好,${name}!`;

console.log(greet("小明")); // "你好,小明!"

这种写法叫调用签名(call signature)。它跟 type GreetFn = (name: string) => string 效果一样,但接口版本可以额外挂属性:

interface GreetFn {
  (name: string): string;
  description: string;
  defaultName: string;
}

// 函数也是对象,可以挂属性
const greet = ((name: string) => `你好,${name}!`) as GreetFn;
greet.description = "一个简单的问候函数";
greet.defaultName = "世界";

这种”函数带属性”的模式在第三方库里挺常见(比如 jQuery 的 $ 既是函数又有 $.ajax 等方法)。

索引签名

有些对象的键名是动态的——比如用户字典、配置映射表。你没法提前列出所有键,但你知道值的类型。这时候用索引签名:

interface StringDict {
  [key: string]: string;
}

const dict: StringDict = {
  hello: "你好",
  world: "世界",
  foo: "哎哟",
};

dict["bar"] = "巴";    // ✅ 动态添加也没问题
// dict["count"] = 42;  // ❌ 值必须是 string

索引签名的语法是 [key: string]: 值类型key 只是个占位名,写 [k: string][prop: string] 都行。

数字索引

索引键也可以是 number

interface StringArray {
  [index: number]: string;
}

const arr: StringArray = ["零", "壹", "贰"];
console.log(arr[1]); // "壹"

不过要注意:JavaScript 里所有对象的键最终都是字符串,arr[0] 实际上访问的是 arr["0"]。TypeScript 区分数字索引主要是为了配合数组的使用习惯。第 26 章会深入讲数字索引和字符串索引之间的约束关系。

索引签名 + 已知属性混用

你可以把索引签名和明确的属性写在一起:

interface UserRecord {
  id: number;               // 固定属性
  name: string;             // 固定属性
  [key: string]: string | number; // 其他动态属性
}

const rec: UserRecord = {
  id: 1,
  name: "Alice",
  role: "admin",    // ✅ 动态属性,值是 string
  score: 95,        // ✅ 动态属性,值是 number
  // isAdmin: true,  // ❌ boolean 不在 string | number 里
};

一个重要的约束:所有固定属性的类型,必须能赋值给索引签名的值类型。 比如上面 id: numbername: string 都能赋值给 string | number,所以没问题。如果索引签名写的是 [key: string]: string,那 id: number 就会报错,因为 number 不能赋值给 string

接口描述对象形状

总结一下,一个接口就是一份”对象使用说明书”。它告诉所有使用这个类型的代码:

  • 这个对象有哪些属性
  • 每个属性是什么类型
  • 哪些属性必须有、哪些可以没有
  • 哪些属性创建后不能改
  • 动态的键对应什么类型的值

以现实中的例子类比的话,接口就像一份租房合同——合同上写了租期、租金、押金这些条款,签了就得照办。TypeScript 的接口对于对象来说,就是这样的合同。

interface Product {
  readonly id: number;
  name: string;
  price: number;
  description?: string;
  tags: string[];
  [customField: string]: unknown;
}

const item: Product = {
  id: 1,
  name: "无线耳机",
  price: 299,
  tags: ["电子", "音频"],
  color: "黑色",    // 索引签名兜底
  weight: "250g",   // 索引签名兜底
};

这个 Product 接口清晰地定义了:id 只读、name 和 price 必填、description 可选、tags 是字符串数组,其余字段可以是任意类型。

小结

  • interface 给对象形状起名,便于复用,编译后零开销。
  • 可选属性 ? 在 strict 模式下类型包含 undefined
  • readonly 防止属性被重新赋值,但只约束第一层。
  • 调用签名 (参数): 返回值 让接口能描述函数类型。
  • 索引签名 [key: string]: T 处理动态键名的对象。
  • 固定属性必须兼容索引签名的值类型。

接口已经很强了,但 TypeScript 还有更厉害的:接口可以继承其他接口。下一章见。