Type vs Interface:读完这篇就没有面试官能难倒你了

Type vs Interface:读完这篇就没有面试官能难倒你了

引言

在 TypeScript 中,typeinterface 是日常开发里最高频出现的两个关键词。它们都能描述对象的结构,但底层机制有本质区别。面试官最爱问的 TS 问题,十有八九就是这个。本文将从一个 React 函数组件 的实际场景出发,结合笔记和代码,把它们的异同一次性讲透。


一、共同点:都能描述"对象的形状"

先看一段最基础的代码:

ini 复制代码
// 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" };

共同之处在于interfacetype 都可以:

  • 描述一个对象的结构------包含哪些属性,每个属性是什么类型
  • 用于函数参数的类型注解
  • 用于函数返回值类型
  • 给变量、对象做类型约束

它们在 对象形状描述 这个层面,语法不同但能力等价。


二、区别一:继承机制完全不同

这是一个底层差异------两者实现"继承/组合"的方式截然不同。

ini 复制代码
interface Person {
  name: string;
}

// interface 用 extends 继承,像 Java 的面向对象
interface Employee extends Person {
  job: string;
}

// type 用交叉类型 & 组合
type PersonType = { name: string };
type EmployeeType = PersonType & { job: string };

const e1: Employee = { name: "张三", job: "字节开发工程师" };
const e2: EmployeeType = { name: "李四", job: "大厂的苗子" };

关键理解

interface type
语法 extends(继承) &(交叉类型)
语义 "我是一个 Person,并且多了 job" "把 PersonType 的所有属性 和 { job } 合并"
背后 面向接口编程的 名义类型 思路 类型层面的 结构运算

extends 背后是 TypeScript 对传统 OOP 的致敬------它显式声明了 Employee Person 的子类型。而 & 是一种类型代数运算,它不关心"谁是谁的子类型",只是把两个类型的属性集合做并集


三、区别二:声明合并------interface 的"按揭声明"能力

这是 interface 最独特的能力,也是面试的高频考点:

typescript 复制代码
interface Animal {
  name: string;
}

// 同名 interface 会自动合并!
interface Animal {
  age: number;
}

const dog: Animal = { name: "三寸钉子", age: 2 }; // ✅ 两个属性都必须有

// type 呢?
type AnimalType = { name: string };
type AnimalType = { age: number }; // ❌ 报错!标识符重复

为什么会这样?

TypeScript 编译器对 interface 做了特殊处理------遇到同名声明时,会自动合并成员 而非视为重复定义。这叫 Declaration Merging(声明合并)

这个设计的价值在于:

typescript 复制代码
场景:你引用了一个第三方库的类型定义,想在不修改源码的情况下补充属性。

第三方 .d.ts:
  interface Window { appName: string }

你的代码:
  interface Window { appVersion: number }  // 合并,不报错!

使用时:
  window.appName     // ✅
  window.appVersion  // ✅

type 不行,因为 type 本质上是一个类型别名 ------对已有类型的引用。同一个作用域内,同一个名字不能指向两个不同的东西,这和 const 不可重复声明是同一个道理。


四、区别三:type 能表示的东西 interface 做不到

这是 type 的不可替代之处------它能描述非对象类型

typescript 复制代码
// 联合类型 ------ interface 做不到
type ID = string | number;

// 元组类型 ------ interface 做不到
type Point = [number, number];

// interface 试一下?
interface ID {}  // 空的,完全无法表达 "string 或 number" 这个语义
typescript 复制代码
          interface 能表达的          type 能表达的
        ┌─────────────────┐    ┌───────────────────────┐
        │   对象结构        │    │   对象结构              │
        │   { a: string }  │    │   { a: string }        │
        │                  │    │                        │
        └─────────────────┘    │   联合类型 string|number │
                               │   元组     [number,number]│
                               │   字面量   'a'|'b'      │
                               │   映射类型 等等...        │
                               └───────────────────────┘

理解本质type类型别名 ,它可以指向任意类型表达式------对象类型、联合类型、元组、原始类型、字面量类型......而 interface 的设计目的就是定义对象的契约,它天然只能描述对象形状。


五、区别四:函数类型------都能写,type 更简洁

typescript 复制代码
// interface 定义函数类型 ------ 需要写完整的方法签名
interface AddFN {
  (a: number, b: number): number;
}
const add1: AddFN = (x, y) => x + y;

// type 定义函数类型 ------ 像箭头函数一样自然
type AddType = (a: number, b: number) => number;

两者都能表达函数类型,但 type 的语法更接近日常写函数的方式,理解成本更低。interface 则需要写带调用签名的对象------它依然是把函数当作"可调用的对象"来描述,这是 OOP 语系下的思维惯性。


六、实战:React 函数组件里为什么首选 interface

回到实际工程。在 React + TypeScript 项目中,组件 Props 几乎都是用 interface 定义的

typescript 复制代码
// 接口 ------ 传统 OOP 核心概念:抽象
// JS 本身是原型式的、函数式一等公民
// TS 为大型企业级开发提供强类型,思路偏 Java 传统 OOP
// class extends / implements interface
// 面向接口编程 ------ 父子组件之间的数据契约

interface User {
  name: string;
  age: number;
  avatarUrl: string;
}

interface UserCardProps {
  user: User;
  onEdit: (id: number) => void;
}

const UserCard: React.FC<UserCardProps> = ({ user, onEdit }) => {
  return (
    <div>
      <img src={user.avatarUrl} alt={user.name} />
      <span>{user.name} · {user.age}岁</span>
      <button onClick={() => onEdit(user.id)}>编辑</button>
    </div>
  );
};

为什么函数组件的 Props 要选 interface?

  1. 语义对齐 :Props 天然是"契约"------父组件说"我提供这些数据",子组件说"我需要这些数据"。interfaceextends 继承和 OOP 的接口编程一脉相承,表达的正是这个"契约"概念。

  2. 可扩展性 :用 interface 定义的 Props 可以被 extends 扩展出更具体的变体,这和多态组件模式天然匹配:

    kotlin 复制代码
    interface BaseCardProps { user: User }
    interface AdminCardProps extends BaseCardProps { permissions: string[] }
  3. 声明合并 :第三方组件库的 Props 类型,你可以通过 interface 的同名合并能力无缝扩展,而不用改库代码。

  4. 官方与社区惯例 :React 类型定义本身(React.FCReact.ComponentProps 等)大量使用 interface,社区主流项目(Ant Design、Material UI)的组件 Props 也清一色 interface。跟社区走,可读性最高。


七、一张图总结

go 复制代码
                        选择指南
                          │
         ┌────────────────┼────────────────┐
         │                │                │
    需要描述对象形状?   需要联合/元组?   不确定?
         │                │                │
    ┌────▼────┐      ┌────▼────┐      ┌────▼────┐
    │interface│      │  type   │      │interface│
    │         │      │         │      │ (默认)   │
    │ React   │      │ 工具类型│      │ 可扩展性│
    │ Props   │      │ 灵活组合│      │ 更友好  │
    │ 契约设计│      │ 类型体操│      │         │
    └─────────┘      └─────────┘      └─────────┘

一句话记忆

interface 定义"契约"(对象能做什么),type 创造"别名"(这个东西叫什么)。写组件 Props 用 interface,写工具类型用 type

两者既互补又互有交叉,理解它们的底层差异,你才能在代码评审时说清楚"这里为什么选这个"------而不只是"我看别人都这么写"。

相关推荐
油丶酸萝卜别吃2 小时前
jquery-ajax.js 说明文档
前端·javascript·jquery
windliang2 小时前
Claude Code 源码分析(九):子 Agent 如何分叉、继续与回到父会话
前端·javascript·面试
柒和远方3 小时前
V063: TS 面试必考:interface 与 type 的四大差异,与 LLM Harness 的自动化择优
前端·javascript
半个落月3 小时前
React useRef 详解:DOM 引用、持久化值与 Worker 实例
前端·react.js
阿黎梨梨3 小时前
TypeScript 类型编程:从新手到 Harness 工程实践
前端
黄金决明子3 小时前
Vue3 + Vite 打包后打开空白?
前端
张龙6873 小时前
终端效率翻倍实战:fzf + zoxide + ripgrep + bat 组合拳,告别重复敲命令
前端
宿6743 小时前
vue3-env环境
前端·vue.js
蔬菜_3 小时前
前端转全栈-day5(数组、list、set)
java·前端·数据结构·list