TS interface 和 type 的使用方法

459 阅读5分钟

核心共同点:定义对象/结构的形状

type 和 interface 最常见的用途都是用来描述一个对象应该包含哪些属性、方法以及它们的类型。

// 使用 interface 定义
interface PointInterface {
  x: number;
  y: number;
}

// 使用 type 定义
type PointType = {
  x: number;
  y: number;
};

// 使用它们来注解变量
let p1: PointInterface = { x: 10, y: 20 };
let p2: PointType = { x: 30, y: 40 };

// 它们都可以描述可选属性、只读属性和方法
interface UserInterface {
  readonly id: number;
  name: string;
  email?: string; // 可选
  greet(): string;
}

type UserType = {
  readonly id: number;
  name: string;
  email?: string; // 可选
  greet: () => string; // 方法的类型定义方式略有不同,但效果一样
};
    

interface (接口)详解

  1. 主要用途: 定义对象、类(通过 implements)的结构契约。

  2. 语法: 使用 interface 关键字。

    interface InterfaceName {
        propertyName: Type;
        optionalProperty?: Type;
        readonly readonlyProperty: Type;
        methodName(param: Type): ReturnType;
    }
        
    
  3. 特点:

    • 擅长描述对象形状: 这是它的核心强项。

    • 可继承 (extends) : 一个接口可以继承一个或多个其他接口,合并它们的成员。

      interface Animal { name: string; }
      interface Dog extends Animal { breed: string; } // Dog 继承 Animal
      
      let myDog: Dog = { name: "Buddy", breed: "Golden" };
          
      
    • 可被类实现 (implements) : 类可以使用 implements 关键字来确保它符合某个接口定义的契约。

      interface ClockInterface { currentTime: Date; }
      class DigitalClock implements ClockInterface {
          currentTime: Date = new Date();
      }
          
      
    • 声明合并 (Declaration Merging) : 这是 interface 最独特的特性之一。如果你在同一个作用域内定义了多个同名的 interface,它们会自动合并成一个单一的接口。这对于扩展已有的接口(甚至是第三方库的接口)非常有用。

      // 第一次声明
      interface Box {
          height: number;
          width: number;
      }
      // 第二次声明 (在同一作用域或通过模块扩展)
      interface Box {
          scale: number;
          // height: string; // Error: Subsequent property declarations must have the same type.
      }
      // 合并后的 Box 接口等同于:
      // interface Box {
      //   height: number;
      //   width: number;
      //   scale: number;
      // }
      let box: Box = { height: 5, width: 6, scale: 10 };
          
      

type (类型别名)详解

  1. 主要用途: 给任何类型起一个新名字(别名)。

  2. 语法: 使用 type 关键字和 = 赋值。

          type AliasName = ExistingType;
        
    
  3. 特点:

    • 极其灵活: type 可以为任何类型创建别名,包括:

      • 原始类型: type MyString = string;
      • 联合类型: type ID = string | number;
      • 交叉类型: type PointAndColor = PointType & { color: string };
      • 元组类型: type PointTuple = [number, number];
      • 函数类型: type MathFunc = (x: number, y: number) => number;
      • 对象字面量类型 (如上面 PointType 示例)
      • 甚至其他类型别名: type UserID = ID;
    • 常用于联合和交叉类型: type 在定义联合类型 (|) 和交叉类型 (&) 时比 interface 更直接方便。

      type StringOrNumber = string | number;
      type ClickableAndFocusable = Clickable & Focusable; // Clickable, Focusable 可能是 interface 或 type
          
      
    • 可用于映射类型和条件类型: TypeScript 的高级类型工具(如 Partial, Readonly, Pick<T, K>, 条件类型 T extends U ? X : Y 等)通常与 type 一起使用来创建复杂的新类型。

      type PartialPoint = Partial<PointType>; // { x?: number; y?: number; }
      type StringKeys<T> = { [K in keyof T]: T[K] extends string ? K : never }[keyof T];
          
      
    • 不能声明合并: 如果你尝试定义同名的 type,TypeScript 会抛出错误,它不会像 interface 那样合并。

      type BoxType = { height: number; };
      // type BoxType = { width: number; }; // Error: Duplicate identifier 'BoxType'.
          
      

核心区别总结

特性interface (接口)type (类型别名)
主要用途定义对象/类的结构契约任何类型起别名
适用范围主要用于对象形状、类实现原始类型、联合、交叉、元组、对象、函数等所有类型
声明合并支持 (多个同名接口会自动合并)不支持 (同名 type 会报错)
继承/扩展使用 extends 关键字 (可继承多个)使用交叉类型 & 来组合对象形状
类实现使用 implements 关键字直接实现类可以实现 type 定义的对象形状,但 implements 关联性不如 interface 强
定义复杂类型相对受限非常灵活,常用于联合、交叉、映射、条件类型等

何时使用 interface vs 何时使用 type?(实践建议)

这是一个在 TypeScript 社区中有些争议但也有普遍共识的话题:

  1. 定义公共 API 的对象形状 (例如库的类型定义) :

    • 推荐使用 interface。主要原因是 interface 的声明合并特性允许库的使用者在需要时安全地扩展接口(例如,给全局 Window 接口添加属性)。
  2. 定义应用程序内部的对象形状:

    • 两者皆可。选择哪个更多是团队风格和一致性的问题。
    • 如果你预期未来可能需要合并声明,或者更倾向于面向对象的 extends 和 implements 语法,可以选择 interface。
    • 如果你更喜欢 type 的灵活性,或者需要定义联合、交叉等非对象形状,或者想明确表示“这个类型定义不应该被外部合并”,可以选择 type。
  3. 定义联合类型、交叉类型、元组、函数类型、原始类型别名:

    • 必须使用 type。interface 无法做到这些。
  4. 使用 TypeScript 高级类型工具 (映射类型、条件类型等) :

    • 通常使用 type。这些高级类型操作的结果往往需要用 type 来命名。
  5. 保持一致性:

    • 在一个项目或团队内部,对于定义对象形状,尽量保持一致。如果大部分地方用了 interface,就继续用 interface (除非需要 type 的特定功能);反之亦然。

总结:

  • interface 专注于描述对象和类的“契约”,并拥有独特的“声明合并”能力。
  • type 是一个更通用的“类型别名”工具,适用于所有类型,尤其擅长处理联合、交叉和利用高级类型工具。

两者在定义对象形状方面有很多重叠,选择哪个往往取决于具体需求(是否需要合并?是否需要定义非对象类型?)以及团队的编码规范。理解它们的核心区别(尤其是声明合并和适用范围)是做出正确选择的关键。