前言
兄弟们,上周帮隔壁组 Review 代码,差点没把我气笑。打开项目一看,通篇的 any,把 TypeScript 玩成了 AnyScript。问他为啥不用类型,他说:“太麻烦了,定义不出来,不如直接 any 一把梭。”
卧槽,这牛逼! 你这是把 TS 当 JS 写,完全放弃了类型系统的编译时检查,不仅开发体验极差(没有自动补全),上线后还全是 undefined is not a function 的运行时报错。
其实,TypeScript 的类型系统极其强大,很多时候不是“定义不出来”,而是我们没掌握“类型体操”的姿势。今天我就把压箱底的 8 个 TypeScript 高级实战技巧 分享出来。这些技巧不仅能让你的代码健壮性提升一个档次,更是面试中装逼的神器。
学会这些,下次面试官问你“TS 有什么高级用法”时,你直接甩出这几个案例,保证让他沉默!🔥
第一部分:为什么你觉得 TS 难用?
很多兄弟觉得 TS 难用,无非是遇到下面这两种情况:
- 后端数据结构太烂:后端返了一堆动态 key 的 JSON,你不知道怎么定义类型。
- 复用性差:写了一堆
interface,结果稍微变个字段又要重写一个,累觉不爱。
其实,TS 的核心威力在于“类型推导”和“类型变换” 。只要你掌握了如何从值反推类型,如何用现有类型生成新类型,写 TS 比写 JS 还爽!
第二部分:核心方案详解(实战干货)
1. 拒绝硬编码:巧用 keyof typeof 自动抓取键名 🚀
你是不是经常手动定义一堆字符串字面量类型?比如定义颜色、API 状态码。一旦常量改了,还得去改类型定义,很容易这就不同步了。
❌ 传统笨办法:
const colors = {
red: 'reddish',
blue: 'bluish'
};
// 每次改 colors 对象,还得手动改这里,累不累?
type Colors = 'red' | 'blue';
✅ 高手方案:
直接利用 typeof 捕获变量类型,再用 keyof 提取键名。代码直接复制就能跑:
const colors = {
red: '#ff0000',
blue: '#0000ff',
green: '#00ff00'
};
// 🔥 卧槽,一行代码搞定,自动同步!
type Colors = keyof typeof colors;
// 测试一下
let color: Colors;
color = 'red'; // ✅ OK
color = 'blue'; // ✅ OK
// color = 'yellow'; // ❌ 报错:Type '"yellow"' is not assignable to type '"red" | "blue" | "green"'.
原理: typeof 获取了 colors 对象的整体结构,keyof 提取了该结构的键名联合类型。这在处理配置项、枚举映射时简直是神器,。
2. 自定义类型守卫 (User Defined Type Guards) 🔥
我们在处理联合类型时,经常需要判断具体是哪种类型。普通的 typeof 往往不够用,特别是处理接口响应时。
❌ 痛点场景:
interface Foo { foo: number; common: string; }
interface Bar { bar: number; common: string; }
function doStuff(arg: Foo | Bar) {
if (arg.foo) { // ❌ 报错:Property 'foo' does not exist on type 'Bar'.
console.log(arg.foo);
}
}
✅ 高手方案:使用 is 关键字
我们可以写一个函数,告诉编译器:“相信我,如果这个函数返回 true,它就是 Foo 类型”。
// 🚀 定义一个类型守卫函数
function isFoo(arg: any): arg is Foo {
return arg.foo !== undefined;
}
function doStuff(arg: Foo | Bar) {
if (isFoo(arg)) {
// ✨ 在这个块里,TS 知道 arg 必定是 Foo,放心用!
console.log(arg.foo); // ✅ OK
// console.log(arg.bar); // ❌ Error,这里不可能是 Bar
} else {
// ✨ 这里的 else 自动推导为 Bar,太智能了!
console.log(arg.bar); // ✅ OK
}
}
原理: arg is Foo 是类型谓词。它通过运行时的检查,实现了编译时的类型收窄,。
3. 彻底消灭 any:泛型 + 类型推导 (Generics)
写通用组件或函数时,不要用 any!泛型就是为了解决“既要通用,又要类型安全”的问题。
🔥 实战场景:一个通用的 API 请求函数
// 定义一个泛型函数,T 代表返回的数据结构
function request<T>(url: string): Promise<T> {
return fetch(url).then(response => response.json());
}
interface User {
id: number;
name: string;
}
// 💥 使用时传入具体类型,瞬间获得自动补全!
request<User>('/api/user/1').then(user => {
console.log(user.name); // ✅ 敲 user. 就会自动提示 name
// console.log(user.age); // ❌ 报错,User 接口里没 age
});
原理: 泛型 <T> 就像是一个占位符,调用时才填入具体类型。这样既保证了函数的通用性,又保留了严格的类型检查。
4. 字符串枚举的替代方案 (String Literals Union)
TypeScript 的 Enum 有时候编译出来的代码比较冗余。很多时候,我们其实只需要一组字符串常量。
✅ 高手方案:组合字面量类型
// 定义一组字面量联合类型
type CardinalDirection = "North" | "East" | "South" | "West";
function move(distance: number, direction: CardinalDirection) {
console.log(`Moving ${distance} meters towards ${direction}`);
}
move(1, "North"); // ✅ OK
// move(1, "Nurth"); // ❌ 报错:拼写错误直接拦截!
这种写法编译后没有任何运行时开销(就是纯字符串),性能最好,也是目前很多开源库推荐的写法,。
5. 只读属性 Readonly:让数据不可变
在 React 或 Redux 开发中,不可变数据(Immutable)非常重要。防止队友手贱修改了 Props 或 State,我们可以用 Readonly。
interface User {
readonly id: number; // 👈 重点在这里
name: string;
}
const u: User = { id: 1, name: 'Jack' };
u.name = 'Rose'; // ✅ OK
// u.id = 2; // ❌ 报错:Cannot assign to 'id' because it is a read-only property.
进阶: 如果想让整个对象都只读,可以用 Readonly<T> 映射类型:
type ReadonlyUser = Readonly<User>;
// 现在 id 和 name 都不能改了!
这在维护大型项目时,能避免很多莫名其妙的 Bug,。
6. 穷尽性检查 (Exhaustive Check):利用 never 兜底 🛡️
这是我面试最爱问的一个点。当你用联合类型(Union Type)做状态管理时,如何保证你在 switch 或 if 中处理了所有可能的情况?如果有人新增了一个状态,怎么让编译器提醒你去改代码?
🔥 神技:利用 never 类型
interface Square { kind: "square"; size: number; }
interface Rectangle { kind: "rectangle"; width: number; height: number; }
interface Circle { kind: "circle"; radius: number; } // 假如这是后来新增的
type Shape = Square | Rectangle | Circle;
function area(s: Shape) {
switch (s.kind) {
case "square": return s.size * s.size;
case "rectangle": return s.width * s.height;
case "circle": return Math.PI * s.radius ** 2;
default:
// 🔥 这里的检查是关键!
// 如果你忘记写 case "circle",s 的类型在这里就不会是 never,TS 就会报错!
const _exhaustiveCheck: never = s;
return _exhaustiveCheck;
}
}
原理: never 表示永远不存在的类型。如果 switch 把所有可能的 kind 都处理了,default 分支里的 s 就应该是 never。如果有遗漏,s 就会剩下某种类型,导致赋值给 never 报错,。
7. 品牌类型 (Nominal Typing):防止类型混用
TS 是“结构化类型”系统(Duck Typing),只要结构一样就认为是同一个类型。但有时候我们希望区分它们,比如 UserId 和 OrderId 都是字符串,但不能混用。
✅ 高手方案:使用“品牌”标记(Branding)
// 利用交叉类型 & 给字符串打个“标签”
type UserId = string & { readonly _brand: unique symbol };
type OrderId = string & { readonly _brand: unique symbol };
// 模拟构造函数
function UserId(id: string) { return id as UserId; }
function OrderId(id: string) { return id as OrderId; }
const myUser = UserId('user_01');
const myOrder = OrderId('order_01');
// myUser = myOrder; // ❌ 报错!虽然本质都是 string,但 TS 认为它们类型不同!
这种技巧在处理复杂的 ID 逻辑、货币计算时非常有用,能防止低级的业务逻辑错误。
8. 巧用 Record 构建映射对象
当你需要定义一个对象,Key 是某种字符串枚举,Value 是另一种类型时,别再手写索引签名了。
❌ 啰嗦写法:
type Page = 'home' | 'about' | 'contact';
interface PageInfo {
title: string;
}
// 如果 Page 加了一个类型,这里忘了加怎么办?
const nav: { [key: string]: PageInfo } = {
home: { title: 'Home' },
// ...
};
✅ 高手方案:Record<K, T>
type Page = 'home' | 'about' | 'contact';
interface PageInfo {
title: string;
}
// 🔥 Record 会强制你必须包含 Page 中定义的所有 Key,少写一个都会报错!
const nav: Record<Page, PageInfo> = {
home: { title: 'Home' },
about: { title: 'About' },
contact: { title: 'Contact' }
};
这能保证你的配置对象和类型定义永远保持同步,少写 bug。
第三部分:进阶扩展——类型体操的魅力
把上面这些技巧组合起来,你就能写出非常强大的泛型工具。
比如,Redux 的类型定义 就是这些技巧的集大成者。它利用了 Discriminated Unions(可辨识联合) 来处理 Action,利用 Generics(泛型) 来推导 State 的变化。
看看这个 Redux 的简化版类型定义,是不是很眼熟?
// Action 是一个联合类型,依靠 type 字段区分
type Action =
| { type: 'INCREMENT' }
| { type: 'DECREMENT' }
| { type: 'SET', payload: number };
function reducer(state: number = 0, action: Action) {
switch (action.type) {
case 'INCREMENT': return state + 1; // ✅ 自动推导
case 'SET': return action.payload; // ✅ TS 知道只有 SET 才有 payload
default: return state;
}
}
掌握了这些,你在看开源库源码(如 Redux, React-Router)时,就不会再对着那一堆 <T, U, V> 发呆了,。
第四部分:总结
类型优化不是玄学,就是细节的积累。
- 多用
keyof typeof,少写硬编码。 - 善用
is类型守卫,运行时和编译时两手抓。 - 泛型
<T>是灵魂,拒绝any。 never兜底,让 Bug 在编译阶段就现原形。
未来趋势预测:随着 TypeScript 5.x 的迭代,类型推导会越来越智能,但核心的结构化类型(Structural Typing) 和 代数数据类型(ADT) 的思想不会变。掌握这些,你离高级工程师只差一个项目的距离!
最后:
觉得有用的话,点赞、收藏、关注三连走一波!🔥 你的支持是我更新的动力!
收藏这篇,随时回来复习,下次面试直接抄走!