写了3年TypeScript,总结几个让代码更干净的实用技巧

最近在重构一个两年前的项目,打开代码的那一刻我差点想删库跑路。变量名叫data1
、data2
,函数动不动就 100 多行,类型全是any
...简直是灾难现场。
痛定思痛,我总结了一些在 TypeScript 项目中让代码更干净的实用技巧。这些都是我在实际项目中踩过坑后总结出来的,希望能帮大家避免写出"屎山"代码。
1. 起个好名字,别让人猜
这个真的太重要了。好的变量名就是最好的注释。
我之前写的垃圾代码:
// 这是什么鬼?
function calculate(a: number, b: number, c: number) {
return a + b * c;
}
现在我会这样写:
function calculateOrderTotal(price: number, quantity: number, taxRate: number): number {
const subtotal = price * quantity;
const taxAmount = subtotal * taxRate;
return subtotal + taxAmount;
}
看到区别了吗?第二个版本不用注释就知道在干什么。
还有一个小技巧:TypeScript 的接口命名,我现在直接用User
而不是IUser
。现代的做法更简洁。
2. 一个函数只做一件事
这个原则听起来简单,但实际项目中很容易违反。我之前经常写出这种"万能函数":
反面教材:
function fetchAndProcessUserData(userId: string) {
// 1. 获取数据
const response = fetch(`/api/users/${userId}`);
// 2. 解析数据
const user = response.json();
// 3. 验证数据
if (!user.name || !user.email) {
throw new Error("Invalid user data");
}
// 4. 保存到数据库
db.save(user);
}
这个函数做了 4 件事!调试的时候简直要命。
改进后:
async function fetchUserData(userId: string): Promise<User> {
const response = await fetch(`/api/users/${userId}`);
return response.json();
}
function validateUserData(user: User): boolean {
return !!(user.name && user.email);
}
async function saveUser(user: User): Promise<void> {
await db.save(user);
}
async function handleUserRequest(userId: string): Promise<void> {
const user = await fetchUserData(userId);
if (validateUserData(user)) {
await saveUser(user);
}
}
现在每个函数职责清晰,测试和维护都容易多了。
3. 别再用 any 了,好好利用 TypeScript 的类型系统
TypeScript 的类型系统是它最大的优势,但我发现很多人(包括以前的我)经常偷懒用any
。
用具体的类型,不要 any
any
就像是给类型系统开了个后门,用多了就失去 TypeScript 的意义了。
善用联合类型和字面量类型
// 这样太宽泛了
type Status = string;
// 这样更精确,IDE还能自动补全
type OrderStatus = "pending" | "processing" | "shipped" | "delivered" | "cancelled";
function handleOrderStatus(status: OrderStatus) {
// TypeScript会检查你是否处理了所有可能的状态
switch (status) {
case "pending":
// ...
break;
case "processing":
// ...
break;
// 如果漏了某个状态,TypeScript会提醒你
}
}
定义清晰的数据结构
interface User {
id: number;
name: string;
email: string;
isActive: boolean;
}
interface Product {
id: string;
name: string;
price: number;
}
这样定义数据模型,整个团队都知道数据长什么样。
4. 错误处理要优雅
以前我经常返回null
或者错误码,现在我更倾向于抛出异常。TypeScript 可以帮我们定义自定义错误类型:
class NetworkError extends Error {
constructor(message: string) {
super(message);
this.name = "NetworkError";
}
}
class ValidationError extends Error {
constructor(message: string) {
super(message);
this.name = "ValidationError";
}
}
async function fetchData(): Promise<Data> {
try {
const response = await fetch("/api/data");
if (!response.ok) {
throw new NetworkError("Failed to fetch data");
}
return response.json();
} catch (error) {
if (error instanceof NetworkError) {
// 处理网络错误
}
throw error;
}
}
5. 写可测试的代码
这个我深有体会。以前写的代码测试起来特别痛苦,后来学会了依赖注入,世界都清净了。
难测试的代码:
class UserService {
private db = new DatabaseClient(); // 直接依赖具体实现
async getUser(id: number): Promise<User> {
return this.db.query(`SELECT * FROM users WHERE id = ${id}`);
}
}
这样的代码测试时必须连真实数据库,太麻烦了。
可测试的代码:
interface IDatabase {
query(sql: string): Promise<any>;
}
class UserService {
constructor(private db: IDatabase) {} // 依赖抽象接口
async getUser(id: number): Promise<User> {
return this.db.query(`SELECT * FROM users WHERE id = ${id}`);
}
}
// 测试时可以轻松mock
const mockDb: IDatabase = {
query: async (sql) => ({ id: 1, name: "Test User" }),
};
const userService = new UserService(mockDb);
总结
写了这么多年代码,我发现最重要的不是炫技,而是让代码容易理解和维护。TypeScript 给了我们很好的工具,关键是要善用。
记住几个要点:
- 起好名字,让代码自己说话
- 函数职责单一,便于测试和维护
- 充分利用类型系统,别偷懒用 any
- 优雅处理错误,定义清晰的异常类型
- 通过依赖注入让代码可测试
这些技巧看起来简单,但在实际项目中坚持下来并不容易。不过一旦养成习惯,你会发现代码质量有质的提升。
最后说一句:代码是写给人看的,机器只是顺便执行一下。投入时间让代码更清晰,未来的你会感谢现在的自己。
关注微信公众号

扫码关注获取:
- • 最新技术文章推送
- • 独家开发经验分享
- • 实用工具和资源
💬 评论讨论
欢迎对《写了3年TypeScript,总结几个让代码更干净的实用技巧》发表评论,分享你的想法和经验