TS入门必看:interface、type、泛型核心用法详解

62 阅读8分钟

TS入门必看:interface、type、泛型核心用法详解

对于刚接触TypeScript(以下简称TS)的小白来说,interface、type、泛型这三个概念大概率会让你头疼——它们看起来有点像,又各有各的用法,不知道什么时候该用哪个。

其实核心逻辑很简单:前两者是用来「描述数据形状」的工具,后者是用来「提升代码复用性、处理通用类型」的工具。今天这篇文章,就用最通俗的语言+最实用的示例,帮你快速搞懂并上手这三个核心知识点。

一、先搞懂:interface 和 type —— 给数据“画个轮廓”

在TS中,我们写代码的核心是「给变量、函数、对象等加上类型约束」,避免出现类型错误。而interface和type,就是用来定义这些“约束规则”的两种方式,本质上都是为了描述数据的形状。

1. 基础用法:interface 怎么用?

interface(接口)的核心作用是「定义对象/类的结构」,告诉TS某个对象/类必须包含哪些属性、方法,以及它们的类型。

语法格式:interface 接口名 { 属性1: 类型1; 属性2: 类型2; 方法?: 类型3; }(问号表示可选属性)

举个栗子(定义用户对象结构):

// 定义一个User接口,描述用户对象的结构
interface User {
  id: number; // 必选属性:id是数字类型
  name: string; // 必选属性:name是字符串类型
  age?: number; // 可选属性:age是数字类型,可传可不传
  sayHello(): void; // 必选方法:无返回值
}

// 使用接口约束变量
const user1: User = {
  id: 1,
  name: "张三",
  sayHello() {
    console.log(`你好,我是${this.name}`);
  }
};

// 错误示例:缺少必选属性id
const user2: User = {
  name: "李四",
  sayHello() {}
};

这里的User接口就像一个“模板”,规定了所有标注为User类型的对象,必须符合这个模板的结构——少了必选属性、属性类型不对,TS都会直接报错,帮我们提前规避错误。

2. 基础用法:type 怎么用?

type(类型别名)的作用是「给一个或多个类型起一个新名字」,它的适用范围比interface更广,不仅能描述对象,还能描述基本类型、联合类型、交叉类型等。

语法格式:type 类型别名 = 具体类型;

举几个栗子:

// 1. 给基本类型起别名(简化复杂类型的书写)
type Age = number;
const myAge: Age = 25; // 等价于 const myAge: number = 25;

// 2. 描述对象类型(和interface类似)
type UserType = {
  id: number;
  name: string;
  age?: number;
  sayHello(): void;
};
const user3: UserType = {
  id: 3,
  name: "王五",
  sayHello() {}
};

// 3. 描述联合类型(多个类型可选)
type Status = "success" | "error" | number;
const resStatus: Status = "success"; // 正确
const resStatus2: Status = 404; // 正确

// 4. 描述交叉类型(合并多个类型)
type Person = { name: string };
type Job = { job: string };
type PersonWithJob = Person & Job; // 同时拥有Person和Job的属性
const person: PersonWithJob = {
  name: "赵六",
  job: "前端开发"
};

可以看到,type的用法更灵活,除了对象,还能处理各种复杂的类型组合,这是它和interface的核心区别之一。

3. interface 和 type 的核心区别(小白必记)

很多时候interface和type可以互相替代,但有些场景下必须选其中一个,记住这3点就够了:

  • 适用范围不同:interface只能描述对象/类的结构;type可以描述任意类型(基本类型、联合类型、交叉类型等)。
  • 扩展方式不同:interface通过extends扩展(更直观);type通过 &(交叉类型) 扩展(更灵活)。
  • 重复定义处理不同:interface重复定义会自动合并(适合多人协作扩展);type重复定义会报错(更严格,避免冲突)。

举个扩展的例子:

// interface 扩展
interface Animal {
  name: string;
}
interface Dog extends Animal { // 继承Animal的属性
  bark(): void;
}
const dog: Dog = {
  name: "旺财",
  bark() { console.log("汪汪汪"); }
};

// type 扩展(交叉类型)
type AnimalType = { name: string };
type DogType = AnimalType & { bark(): void }; // 合并两个类型
const dog2: DogType = {
  name: "来福",
  bark() { console.log("汪汪汪"); }
};

小白选型建议:如果是定义对象/类的结构,优先用interface(语义更清晰,支持合并扩展);如果是处理基本类型、联合类型、交叉类型,用type(适用范围更广)。

二、再进阶:泛型 —— 让类型“变灵活”

搞懂了interface和type,接下来看泛型。很多小白觉得泛型难,其实核心是理解它的作用:解决“类型重复定义”的问题,让一个函数/组件/接口能适配多种类型

举个场景:我们需要写一个“获取数组第一个元素”的函数,这个数组可能是数字数组、字符串数组、对象数组……如果不用泛型,我们可能需要写多个重复的函数:

// 处理数字数组
function getFirstNum(arr: number[]): number {
  return arr[0];
}

// 处理字符串数组
function getFirstStr(arr: string[]): string {
  return arr[0];
}

// 处理User数组
interface User { id: number; name: string; }
function getFirstUser(arr: User[]): User {
  return arr[0];
}

这明显很冗余!而泛型的思路是:不提前指定具体类型,而是让用户在使用时传入类型,就像给函数传参数一样。

1. 泛型的基础用法:函数泛型

语法格式:function 函数名<T>(参数: T): T { ... }(T是泛型参数,相当于一个“类型变量”,可以随便起名,比如U、V)

用泛型改写上面的例子:

// 泛型函数:T代表任意类型,用户使用时传入具体类型
function getFirst<T>(arr: T[]): T | undefined {
  return arr[0];
}

// 使用时指定类型(也可以省略,TS会自动推断)
const num = getFirst<number>([1, 2, 3]); // num是number类型
const str = getFirst<string>(["a", "b", "c"]); // str是string类型
const user = getFirst<User>([{ id: 1, name: "张三" }]); // user是User类型

这样一来,一个函数就能适配所有类型的数组,代码瞬间简洁了!这里的T就像一个“占位符”,在函数调用时被具体的类型(number、string、User)替换。

2. 泛型的其他用法:接口/类型别名泛型

泛型不仅能用于函数,还能用于interface和type,让接口/类型别名也能适配多种场景。

举个栗子(定义一个“容器”接口,能装任意类型的数据):

// 泛型接口
interface Container<T> {
  data: T; // data的类型由T决定
  getdata(): T;
}

// 使用时指定T的具体类型
const numContainer: Container<number> = {
  data: 100,
  getdata() { return this.data; }
};

const strContainer: Container<string> = {
  data: "hello",
  getdata() { return this.data; }
};

// 泛型类型别名
type ContainerType<T> = {
  data: T;
  getdata(): T;
};
const userContainer: ContainerType<User> = {
  data: { id: 1, name: "张三" },
  getdata() { return this.data; }
};

3. 泛型约束:给泛型加“限制”

有时候,我们需要限制泛型的范围,不能让它接收任意类型。比如,我们需要写一个“获取对象属性”的函数,要求传入的必须是对象类型,这时候就需要用「泛型约束」。

语法格式:function 函数名<T extends 约束类型>(参数: T): T { ... }

// 约束T必须是对象类型(extends object)
function getProp<T extends object, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

// 正确用法
const user = { id: 1, name: "张三" };
getProp(user, "name"); // 返回"张三",类型是string

// 错误用法:第一个参数不是对象
getProp(123, "id"); // TS报错

// 错误用法:第二个参数不是对象的属性
getProp(user, "age"); // TS报错(user没有age属性)

这里的T extends object表示T必须是对象类型;K extends keyof T表示K必须是T的属性名(keyof T会返回T所有属性名的联合类型)。通过约束,我们让泛型更安全、更精准。

三、实战小案例:结合三者写一段完整代码

最后,我们用一个小案例把interface、type、泛型结合起来,看看实际开发中怎么用:

// 1. 用interface定义用户结构
interface User {
  id: number;
  name: string;
  age?: number;
}

// 2. 用type定义返回结果类型(联合类型)
type Result<T> = {
  code: 200 | 500;
  message: string;
  data: T; // data的类型由泛型T决定
};

// 3. 用泛型函数获取用户列表
function getUserList<T>(): Result<T[]> {
  // 模拟接口请求返回的数据
  return {
    code: 200,
    message: "请求成功",
    data: [{ id: 1, name: "张三" }, { id: 2, name: "李四" }] as T[]
  };
}

// 4. 使用函数,指定T为User类型
const userList = getUserList<User>();
console.log(userList.data[0].name); // 张三

这段代码里,interface定义了核心的User结构,type定义了通用的接口返回格式(Result),泛型让Result和getUserList函数能适配不同类型的数据(比如User、Product等),完美体现了三者的协同作用。

四、小白快速上手总结

  1. interface:专门描述对象/类的结构,支持extends扩展和重复合并,适合定义核心数据模型。
  2. type:给类型起别名,适用范围广(基本类型、联合/交叉类型等),扩展用交叉类型,重复定义会报错,适合处理复杂类型组合。
  3. 泛型:用作为类型变量,让函数/接口/类型别名适配多种类型,减少重复代码;可通过extends加约束,保证类型安全。

记住:不用一开始就追求“精通”,先学会基础用法,在实际开发中多写多练,慢慢就能理解它们的精髓了。如果刚开始分不清interface和type,就先记住「定义对象用interface,其他情况用type」,再逐步细化!