在这篇文章中,分享TypeScript中的5个不良实践以及如何避免它们。
1. 将错误声明为Any类型
示例
在以下代码片段中,我们捕获错误然后将其声明为any类型。
async function asyncFunction() {
try {
const response = await doSomething();
return response;
} catch (err: any) {
toast(`Failed to do something: ${err.message}`);
}
}
为什么这是不好的 ❌
没有保证错误会有一个类型为字符串的message字段。
不幸的是,由于类型断言,代码让我们假设它确实有。
代码在开发环境中可以针对特定测试用例工作,但在生产环境中可能会严重失败。
应该做什么替代方案 ✅
不要设置错误类型。它应该默认为unknown
。
你可以做到以下几点:
选项1: 使用类型守卫检查错误是否为正确的类型。
async functionasyncFunction() {
try {
const response = awaitdoSomething();
return response;
} catch (err) {
const toastMessage = hasMessage(err)
? `Failed to do something: ${err.message}`
: `Failed to do something`;
toast(toastMessage);
}
}
// 我们使用类型守卫首先进行检查
functionhasMessage(value: unknown): value is { message: string } {
return (
value != null &&
typeof value === "object" &&
"message"in value &&
typeof value.message === "string"
);
}
// 你也可以简单地检查错误是否是Error的实例
const toastMessage = err instanceofError
? `Failed to do something: ${err.message}`
: `Failed to do something`;
选项2(推荐): 不要对错误类型做出假设
与其对错误类型做出假设,不如明确处理每种类型并为用户提供适当的反馈。
如果你无法确定具体的错误类型,最好显示完整的错误信息而不是部分细节。
有关错误处理的更多信息,请查看这篇出色的指南:编写更好的错误消息。
2. 函数中有多个连续的相同类型参数
示例
export function greet(
firstName: string,
lastName: string,
city: string,
email: string
) {
// 做一些事情...
}
为什么这是不好的 ❌
// 我们颠倒了firstName和lastName,但TypeScript不会捕获这一点
greet("Curry", "Stephen", "LA", "stephen.curry@gmail.com");
- 在代码审查中,尤其是在看到函数调用之前,更难以理解每个参数代表什么。
应该做什么替代方案 ✅
使用对象参数来明确每个字段的目的,并最小化错误的风险。
export function greet(params: {
firstName: string;
lastName: string;
city: string;
email: string;
}) {
// 做一些事情...
}
3. 函数有多个分支且没有返回类型
示例
function getAnimalDetails(animalType: "dog" | "cat" | "cow") {
switch (animalType) {
case"dog":
return { name: "Dog", sound: "Woof" };
case"cat":
return { name: "Cat", sound: "Meow" };
case"cow":
return { name: "Cow", sound: "Moo" };
default:
// 这确保TypeScript捕获未处理的案例
((_: never) => {})(animalType);
}
}
为什么这是不好的 ❌
- 添加新的
animalType
可能导致返回结构不正确的对象。 - 返回类型结构的更改可能会导致代码的其他部分出现难以追踪的问题。
应该做什么替代方案 ✅
明确指定函数的返回类型:
type Animal = {
name: string;
sound: string;
};
functiongetAnimalDetails(animalType: "dog" | "cat" | "cow"): Animal {
switch (animalType) {
case"dog":
return { name: "Dog", sound: "Woof" };
case"cat":
return { name: "Cat", sound: "Meow" };
case"cow":
return { name: "Cow", sound: "Moo" };
default:
// 这确保TypeScript捕获未处理的案例
((_: never) => {})(animalType);
}
}
4. 添加不必要的类型而不是使用可选字段
示例
type Person = {
name: string;
age: number;
};
typePersonWithAddress = Person & {
address: string;
};
typePersonWithAddressAndEmail = PersonWithAddress & {
email: string;
};
typePersonWithEmail = Person & {
email: string;
};
为什么这是不好的 ❌
应该做什么替代方案 ✅
使用可选字段来保持你的类型简单和可维护:
type Person = {
name: string;
age: number;
address?: string;
email?: string;
};
5. 在不同组件级别使属性变为可选
示例
interface TravelFormProps {
disabled?: boolean;
}
exportfunctionTravelForm(props: TravelFormProps) {
// 使用日期范围选择器组件...
}
interfaceDateRangePickerProps {
disabled?: boolean;
}
functionDateRangePicker(props: DateRangePickerProps) {
// 使用日期选择器组件...
}
interfaceDatePickerProps {
disabled?: boolean;
}
functionDatePicker(props: DatePickerProps) {}
为什么这是不好的 ❌
- 容易忘记传递
disabled
属性,导致部分启用的表单。
应该做什么替代方案 ✅
使共享字段在内部组件中必需。
这将确保正确的属性传递。这对于低级别的组件尤其重要,以便尽早捕获任何疏忽。
在上面的例子中,disabled
现在在所有内部组件中都是必需的。
interface TravelFormProps {
disabled?: boolean;
}
exportfunctionTravelForm(props: TravelFormProps) {
// 使用日期范围选择器组件...
}
interfaceDateRangePickerProps {
disabled: boolean | undefined;
}
functionDateRangePicker(props: DateRangePickerProps) {
// 使用日期选择器组件...
}
interfaceDatePickerProps {
disabled: boolean | undefined;
}
functionDatePicker(props: DatePickerProps) {}
注意:如果你正在为库设计组件,我不推荐这样做,因为必需字段需要更多的工作。
总结
TypeScript是令人惊叹的,但没有工具🛠️是完美的。
避免这5个错误将帮助你编写更干净、更安全、更易于维护的代码。
阅读原文:原文链接
该文章在 2025/1/10 11:09:59 编辑过