同学们好,我是 Eugene(尤金),一个拥有多年中后台开发经验的前端工程师~
(Eugene 发音很简单,/juːˈdʒiːn/,大家怎么顺口怎么叫就好)
你是否也有过:明明学过很多技术,一到关键时候却讲不出来、甚至写不出来?
你是否也曾怀疑自己,是不是太笨了,明明感觉会,却总差一口气?
就算想沉下心从头梳理,可工作那么忙,回家还要陪伴家人。
一天只有24小时,时间永远不够用,常常感到力不从心。
技术行业,本就是逆水行舟,不进则退。
如果你也有同样的困扰,别慌。
从现在开始,跟着我一起心态归零,利用碎片时间,来一次彻彻底底的基础扫盲。
这一次,我们一起慢慢来,扎扎实实变强。
不搞花里胡哨的理论堆砌,只分享看得懂、用得上的前端干货,
咱们一起稳步积累,真正摆脱“面向搜索引擎写代码”的尴尬。
一、开篇:为什么要关心 TS 类型?
日常业务里经常会遇到:
- 类型报错:
Object is possibly 'undefined'、Type 'string' is not assignable to type 'number' - 不知道选什么:
any、unknown、interface、type什么时候用? - 写了很多 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 的对比:
| 特性 | any | unknown |
|---|---|---|
| 可直接调用方法 | ✅ | ❌ 必须先收窄 |
| 可赋给任意类型 | ✅ | ❌ |
| 类型安全 | 无 | 有(需检查后才用) |
建议:拿不到确切类型时,用 unknown 代替 any,通过 typeof、in、类型守卫等方式收窄后再用。
三、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:怎么选?
这是问得最多的一个问题,先看核心区别:
| 特性 | interface | type |
|---|---|---|
| 声明合并 | ✅ 同名可合并 | ❌ 同名会报错 |
| 继承 | 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?.prop、if 判断、! 断言 |
| 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 | 类型别名,可联合/交叉 | 状态枚举、函数类型、复杂组合 |
记住三点:
- 能用具体类型就不用 any,拿不准就用
unknown再收窄。 - 对象结构用 interface,联合/函数/复杂类型用
type。 - 业务里够用就行,不必一开始就追求完美,先让类型系统帮你兜住大部分错误。
把类型选对,编译期就能发现很多问题,后面的重构和维护都会轻松很多。
学习本就是一场持久战,不需要急着一口吃成胖子。哪怕今天你只记住了一点点,这都是实打实的进步。
后续我还会继续用这种大白话、讲实战方式,带大家扫盲更多前端基础。
关注我,不迷路,咱们把那些曾经模糊的知识点,一个个彻底搞清楚。
如果你觉得这篇内容对你有帮助,不妨点赞收藏,下次写代码卡壳时,拿出来翻一翻,比搜引擎更靠谱。
我是 Eugene,你的电子学友,我们下一篇干货见~