TS 基础扫盲:类型、接口、类型别名在业务代码里的最小集合

0 阅读8分钟

同学们好,我是 Eugene(尤金),一个拥有多年中后台开发经验的前端工程师~

(Eugene 发音很简单,/juːˈdʒiːn/,大家怎么顺口怎么叫就好)

你是否也有过:明明学过很多技术,一到关键时候却讲不出来、甚至写不出来?

你是否也曾怀疑自己,是不是太笨了,明明感觉会,却总差一口气?

就算想沉下心从头梳理,可工作那么忙,回家还要陪伴家人。

一天只有24小时,时间永远不够用,常常感到力不从心。

技术行业,本就是逆水行舟,不进则退。

如果你也有同样的困扰,别慌。

从现在开始,跟着我一起心态归零,利用碎片时间,来一次彻彻底底的基础扫盲

这一次,我们一起慢慢来,扎扎实实变强。

不搞花里胡哨的理论堆砌,只分享看得懂、用得上的前端干货,

咱们一起稳步积累,真正摆脱“面向搜索引擎写代码”的尴尬。

一、开篇:为什么要关心 TS 类型?

日常业务里经常会遇到:

  • 类型报错Object is possibly 'undefined'Type 'string' is not assignable to type 'number'
  • 不知道选什么anyunknowninterfacetype 什么时候用?
  • 写了很多 TS 却像在写 JS:到处用 any,类型形同虚设

TypeScript 的核心是「类型约束」,把很多问题在编译期暴露出来。但很多人要么写太多类型最后变玄学,要么只会 any,形同 JS。

这篇文章不讲特别深的底层原理,而是围绕:平时写业务时该怎么选、为什么这么选、容易踩哪些坑。从基础类型 → 接口 → 类型别名 → 实战选型 → 踩坑,一次性理清。

二、基础类型扫盲

先把日常最常用的 5 个类型搞清楚。

类型含义典型用途
string字符串文案、id、枚举值
number数字(含整数、浮点、NaN)数量、金额、分页
boolean布尔开关、状态
any任意类型,不做检查兼容老代码、临时兜底
unknown任意类型,但必须先检查再用比 any 更安全的兜底

2.1 string / number / boolean

这三个是基础原始类型,和 JS 里的用法一致,只是加了一层类型标注:

// 变量声明时标注类型
const name: string = '张三';
const age: number = 25;
const isActive: boolean = true;

// 函数参数和返回值
function greet(name: string): string {
  return `你好,${name}`;
}

function add(a: number, b: number): number {
  return a + b;
}

业务里怎么用:接口返回值、表单字段、状态开关,优先用这三个而不是 any

2.2 any:最自由也最危险

any 表示「任意类型」,TS 不再做类型检查。

let data: any = 'hello';
data = 123;        // OK
data = { a: 1 };   // OK
data.toUpperCase(); // 编译通过,但运行时报错!data 实际是 number

问题any 会关闭类型检查,等于回到裸写 JS,很容易在运行时才发现错误。

适用场景

  • 临时接入老接口、第三方库,还没时间写类型
  • 快速迁移 JS 项目到 TS 时的过渡
  • 已经用 try-catch 等做了安全兜底

建议:能不用就不用,用的话尽量加注释说明原因。

2.3 unknown:比 any 更安全的兜底

unknown 也表示「任意类型」,但使用时必须先「收窄」类型,否则不能直接用。

let data: unknown = getFromApi(); // 不知道接口返回什么

// 直接调用会报错
// data.toString();  // Error: 'data' is of type 'unknown'

// 先判断类型再使用
if (typeof data === 'string') {
  console.log(data.toUpperCase()); // OK
} else if (typeof data === 'object' && data !== null && 'name' in data) {
  console.log((data as { name: string }).name); // 收窄后可安全使用
}

和 any 的对比

特性anyunknown
可直接调用方法❌ 必须先收窄
可赋给任意类型
类型安全有(需检查后才用)

建议:拿不到确切类型时,用 unknown 代替 any,通过 typeofin、类型守卫等方式收窄后再用。

三、interface:描述对象形状

interface 用来描述「对象长什么样」:有哪些属性、什么类型、哪些可选。

3.1 基本用法

// 定义用户接口
interface User {
  id: number;
  name: string;
  age?: number;  // 可选属性
}

// 使用
const user: User = {
  id: 1,
  name: '张三'
  // age 可省略
};

3.2 可选属性、只读属性

interface Config {
  readonly apiUrl: string;  // 只读,不能改
  timeout?: number;         // 可选
}

const config: Config = { apiUrl: 'https://api.example.com' };
// config.apiUrl = 'xxx';  // Error: 只读

3.3 继承

interface BaseUser {
  id: number;
  name: string;
}

interface AdminUser extends BaseUser {
  role: 'admin';
  permissions: string[];
}

const admin: AdminUser = {
  id: 1,
  name: '管理员',
  role: 'admin',
  permissions: ['read', 'write']
};

3.4 索引签名(动态属性)

// 属性名是 string,值是 number
interface StringMap {
  [key: string]: number;
}

const map: StringMap = {
  a: 1,
  b: 2
};

业务场景:后端返回的用户、列表项、配置对象等,用 interface 描述结构最合适。

四、type 类型别名:给类型起个名字

type 用来给任意类型起别名,可以是基础类型、对象、联合类型、函数等。

4.1 基本用法

// 基础类型别名
type UserId = number;
type UserName = string;

// 对象类型
type User = {
  id: UserId;
  name: UserName;
};

// 联合类型(常见于业务)
type Status = 'pending' | 'success' | 'error';
type Theme = 'light' | 'dark';

4.2 联合类型、交叉类型

// 联合:A 或 B
type Result = { success: true; data: any } | { success: false; error: string };

// 交叉:A 且 B 的属性合并
type WithTimestamp = User & { createdAt: Date };

4.3 函数类型

type OnChange = (value: string) => void;
type FetchUser = (id: number) => Promise<User>;

业务场景:状态枚举、回调类型、联合类型等,用 type 更合适。

五、interface vs type:怎么选?

这是问得最多的一个问题,先看核心区别:

特性interfacetype
声明合并✅ 同名可合并❌ 同名会报错
继承extends& 交叉类型
适用对象对象结构任意类型
扩展对象容易容易
联合/交叉不常用常用

5.1 声明合并(interface 独有)

// interface 同名会合并
interface User {
  name: string;
}
interface User {
  age: number;
}
// 等价于 { name: string; age: number }

// type 同名会报错
type User = { name: string };
type User = { age: number };  // Error: 重复声明

业务含义:写插件、扩展第三方类型定义时,用 interface 可以多次补充属性;而 type 只能定义一次。

5.2 选型建议

用 interface

  • 描述对象结构(用户、配置、接口返回值等)
  • 有继承需求(如 extends BaseUser
  • 可能被第三方或插件扩展(依赖声明合并)

用 type

  • 联合类型:'pending' | 'success' | 'error'
  • 交叉类型:User & { role: string }
  • 函数类型:(id: number) => Promise<User>
  • 元组、复杂组合类型

实践中:对象结构优先 interface,其它复杂类型用 type。两者都能描述对象时,很多团队会统一用 interface,可读性更好。

六、实战场景:该怎么写

6.1 接口返回值

// 用 interface 描述
interface UserListItem {
  id: number;
  name: string;
  avatar?: string;
  status: 'active' | 'inactive';
}

interface ApiResponse<T> {
  code: number;
  message: string;
  data: T;
}

// 使用
async function fetchUserList(): Promise<ApiResponse<UserListItem[]>> {
  const res = await axios.get('/api/users');
  return res.data;
}

6.2 表单、状态枚举

// 用 type 做联合
type FormStatus = 'draft' | 'submitting' | 'success' | 'error';
type SortOrder = 'asc' | 'desc';

interface FilterState {
  status: FormStatus;
  sortBy: string;
  sortOrder: SortOrder;
}

6.3 事件回调

type OnSearch = (keyword: string) => void;
type OnPageChange = (page: number, size: number) => void;

interface TableProps {
  onSearch: OnSearch;
  onPageChange: OnPageChange;
}

6.4 拿不准类型时用 unknown

async function fetchData(url: string): Promise<unknown> {
  const res = await fetch(url);
  return res.json();
}

// 使用时必须收窄
const data = await fetchData('/api/config');
if (data && typeof data === 'object' && 'theme' in data) {
  const theme = (data as { theme: string }).theme;
  // 安全使用
}

七、踩坑指南

原因建议
到处用 any,类型失效any 关闭类型检查尽量用 unknown,或用具体类型
Object is possibly 'undefined'可能为 undefined 却直接访问可选链 obj?.propif 判断、! 断言
interface 和 type 混用一团团队没约定对象用 interface,联合/函数用 type
对象字面量多了属性报错多余属性检查用变量接收再传入,或加索引签名
第三方库没有类型老库、非 TS 编写.d.ts@ts-ignore,标注原因

7.1 多余属性检查

interface User {
  id: number;
  name: string;
}

// 直接传字面量时,多了属性会报错
// createUser({ id: 1, name: '张三', age: 18 });  // Error

// 用变量接收再传则不会(会按结构兼容)
const user = { id: 1, name: '张三', age: 18 };
createUser(user);  // OK

7.2 类型断言要谨慎

// as 断言:你说它是什么,TS 就信
const data = getData() as User;  // 若实际不是 User,运行时可能崩

// 更安全的做法:用类型守卫
function isUser(obj: unknown): obj is User {
  return obj !== null && typeof obj === 'object' && 'id' in obj && 'name' in obj;
}

八、小结

概念一句话典型场景
string/number/boolean基础类型,优先用接口字段、函数参数、状态
any任意类型,无检查临时兜底、兼容老代码,少用
unknown任意类型,用前须收窄拿不准类型时的安全选择
interface描述对象结构用户、配置、接口返回值
type类型别名,可联合/交叉状态枚举、函数类型、复杂组合

记住三点:

  1. 能用具体类型就不用 any,拿不准就用 unknown 再收窄。
  2. 对象结构用 interface,联合/函数/复杂类型用 type
  3. 业务里够用就行,不必一开始就追求完美,先让类型系统帮你兜住大部分错误。

把类型选对,编译期就能发现很多问题,后面的重构和维护都会轻松很多。


学习本就是一场持久战,不需要急着一口吃成胖子。哪怕今天你只记住了一点点,这都是实打实的进步。

后续我还会继续用这种大白话、讲实战方式,带大家扫盲更多前端基础。

关注我,不迷路,咱们把那些曾经模糊的知识点,一个个彻底搞清楚。

如果你觉得这篇内容对你有帮助,不妨点赞收藏,下次写代码卡壳时,拿出来翻一翻,比搜引擎更靠谱。

我是 Eugene,你的电子学友,我们下一篇干货见~