2. 使用接口约束对象结构并实现数据校验

使用接口约束对象结构并实现数据校验 学习卡片

为什么需要接口约束对象结构

在 TypeScript 中,对象字面量很容易写错字段名或漏掉字段,而普通函数参数若用 any 或宽泛类型接收,错误只能在运行时暴露。接口(interface)用于描述对象应具备的字段名、字段类型以及可选/只读属性,让编译器在开发阶段就检查对象结构。

下面定义一个描述用户对象的接口,并编写接收该接口类型的校验函数。

// user.ts
interface User {
  readonly id: number;      // 只读,创建后不可修改
  name: string;             // 必填
  age: number;              // 必填
  email?: string;           // 可选
}

// 接收 User 接口类型,返回校验结果
function validateUser(user: User): string[] {
  const errors: string[] = [];

  if (user.name.trim().length === 0) {
    errors.push("name 不能为空");
  }
  if (user.age < 0 || user.age > 150) {
    errors.push("age 必须在 0~150 之间");
  }
  if (user.email !== undefined && !user.email.includes("@")) {
    errors.push("email 格式不正确");
  }

  return errors;
}

// 正确对象:编译通过
const u1: User = { id: 1, name: "Alice", age: 28, email: "alice@example.com" };
console.log(validateUser(u1)); // []

// 错误对象:编译期即报错
// const u2: User = { id: 2, name: "Bob" };
//   类型 "{ id: number; name: string; }" 缺少属性 "age"

// const u3: User = { id: 3, name: "Carol", age: "30" };
//   不能将类型 "string" 分配给类型 "number"
接口只存在于编译期,编译后的 JavaScript 中不会保留 interface User,因此运行时校验仍需在函数内部手动编写。

编译期检查与运行时校验的分工

接口负责“结构是否匹配”,函数内部逻辑负责“值是否合法”。两者结合才能既防止字段缺失/类型错误,又防止业务规则错误。下图展示从对象字面量到最终校验结果的完整链路,以及编译期与运行时的分界。

编译期(tsc 类型检查) 对象字面量 { id: 1, name: "Alice", age: 28 } interface User id: number (readonly) name: string age: number email?: string 结构比对 字段名匹配? 类型兼容? 可选/只读? 编译报错 字段缺失 / 类型不符 编译通过 生成 .js 文件 运行时(validateUser 函数内部) name 非空检查 trim().length === 0 → "name 不能为空" age 范围检查 age < 0 || age > 150 → "age 必须在 0~150 之间" email 格式检查 !email.includes("@") → "email 格式不正确" errors string[] [] 表示通过

关键检查点与常见错误

错误写法编译器提示修正方式
{ id: 2, name: "Bob" }缺少属性 "age"补上 age: number
{ id: 3, name: "Carol", age: "30" }不能将 string 分配给 number改为 age: 30
user.id = 99无法分配到 "id",因为它是只读属性去掉 readonly 或不在外部修改
{ id: 4, name: "Dave", age: 25, phone: "123" }对象字面量只能指定已知属性删除多余字段或在接口中声明
接口的“多余属性检查”只对对象字面量直接赋值生效。若先把对象赋给一个变量再传入,多余字段不会报错,这是 TypeScript 的结构化类型特性,不是 bug。

验证方式

  1. 运行 npx tsc --noEmit user.ts,确认正确对象无报错。
  2. 取消注释 u2 或 u3,再次运行,观察编译器给出的字段缺失或类型不匹配提示。
  3. 编译为 JavaScript 后运行 node user.js,确认 validateUser(u1) 输出空数组。
  4. 构造 age: 200 或 email: "bad" 的对象,观察运行时返回的错误信息。