TypeScript 编程最佳实践
本文将介绍 TypeScript 编程中的最佳实践。
本指南介绍如何利用 TypeScript 类型来减少 bug 并编写更易读代码的实用最佳实践。
YouTube Video
TypeScript 编程最佳实践
TypeScript 最大的优势是“通过类型防止 bug 并明确代码意图”。
最佳实践不仅仅是规则,更是一系列书写安全、易读和易维护代码的原则。下面通过实际例子介绍常用的 TypeScript 最佳实践。
避免使用 any,并始终为类型赋予有意义的定义
首先,让我们看看“避免使用 any 并赋予有意义类型”这一点。
any 会完全禁用类型检查,这违背了使用 TypeScript 的初衷。不要只用 any 来“暂时解决问题”,而应该尽量给予类型详细的描述。
1// Bad
2function parse(data: any) {
3 return data.value;
4}这段代码可以接受任何值,因此无法避免运行时错误。
1// Good
2type ParsedData = {
3 value: string;
4};
5
6function parse(data: ParsedData): string {
7 return data.value;
8}通过定义类型,可以明确输入输出的意图并提升安全性。
始终使用 type 或 interface 明确定义对象结构。
接下来,让我们看看“始终使用 type 或 interface 明确定义对象结构”这一点。
如果临时使用对象,其结构可能变得不明确。始终提取类型以便复用和维护。
1// Bad
2function createUser(user: { name: string; age: number }) {
3 console.log(user.name);
4}即使在小的代码片段中,养成类型分离的习惯也很重要。
1// Good
2type User = {
3 name: string;
4 age: number;
5};
6
7function createUser(user: User): void {
8 console.log(user.name);
9}为类型命名后,更容易理解整体代码结构。
使用联合类型精确表示所有可能的状态。
如果用裸 string 或 number 类型进行条件判断,可能会出现意外值。通过使用联合类型,你可以在类型层面只表示允许的状态。
1// Bad
2function setStatus(status: string) {
3 console.log(status);
4}在这段代码中,无意义的字符串或错误的值在编译时无法被检测出来。
1// Good
2type Status = "idle" | "loading" | "success" | "error";
3
4function setStatus(status: Status): void {
5 console.log(status);
6}通过使用联合类型,你可以在编译时可靠地消除“不可能的状态”。因此,条件分支的安全性和代码的可靠性都会提高。
显式处理 null 和 undefined。
接下来,让我们看看“显式处理 null 和 undefined”这一点。
在 TypeScript 中,用类型表达值可能不存在是很重要的。如果含糊其词,可能导致运行时错误。
1type User = {
2 name: string;
3 email?: string;
4};email 可能不存在,所以应以此为前提进行处理。
1function printEmail(user: User): void {
2 if (user.email) {
3 console.log(user.email);
4 }
5}在使用可选值之前应始终进行检查。
类型断言(as)只能作为最后的手段使用。
接下来,让我们看看“不滥用类型断言”这一点。
类型断言会临时绕过 TypeScript 的类型检查,用于声明“我知道这个值是这个类型。”。滥用类型断言会破坏类型安全。
1// Bad
2const value = input as string;在这段代码中,即使实际值不是字符串,也不会报错,这可能导致运行时错误。如下例所示,请优先选择通过类型保护进行安全检查。
1// Good
2function isString(value: unknown): value is string {
3 return typeof value === "string";
4}
5
6if (isString(input)) {
7 console.log(input.toUpperCase());
8}类型保护是一种在检查实际值的同时安全判断类型的机制。通过优先使用类型保护而不是类型断言,可以更容易地防止运行时错误。
不要过度依赖返回值类型的类型推断。
接下来,让我们看看“不过度依赖返回类型的推断”这一点。
TypeScript 的类型推断很强大,但明确标注公有函数的返回类型会更安全。这样可以将未来变更的影响降到最低。
1// Bad
2function sum(a: number, b: number) {
3 return a + b;
4}即使在小型函数中也要明确表达你的意图。
1// Good
2function sum(a: number, b: number): number {
3 return a + b;
4}显式写出返回类型能提升 API 的稳定性。
通过使用 unknown 类型安全地处理输入。
接下来,让我们看看“用 unknown 安全地接收外部输入”这一点。
对于 API、JSON 或用户输入等外部数据,请使用 unknown 而不是 any。这样可以确保所有的值都经过验证,从而保持类型安全。
1// Bad
2function handleResponse(data: any) {
3 console.log(data.id);
4}以下是如何用 unknown 验证类型的方法。
1// Good
2function handleResponse(data: unknown): void {
3 if (
4 typeof data === "object" &&
5 data !== null &&
6 "id" in data
7 ) {
8 console.log((data as { id: number }).id);
9 }
10}unknown 不能直接使用;它是一种需要进行验证的类型。在处理外部输入时,这种做法特别有效。
通过组合小型类型来增强表达能力。
接下来,让我们看看“通过组合小类型来增强表达能力”这一点。
一次性定义大型类型会降低可读性和可维护性。将类型拆分成有意义的单元,并根据需要进行组合。
1type Id = number;
2
3type UserProfile = {
4 id: Id;
5 name: string;
6};
7
8type UserWithStatus = UserProfile & {
9 status: "active" | "inactive";
10};将类型当作组件可以帮助你更好地组织设计。
type 和 interface
interface 的优势
type 和 interface 都能定义类型,但它们的用途和特性各不相同。根据合适的用途使用它们,可以让类型定义的意图更加清晰。
1// Bad
2type User = {
3 id: number;
4 name: string;
5};
6
7type AdminUser = {
8 id: number;
9 name: string;
10 role: "admin";
11};如果像这样重复公共部分,代码会因变更而变得脆弱。
1// Good
2interface User {
3 id: number;
4 name: string;
5}
6
7interface AdminUser extends User {
8 role: "admin";
9}interface 非常适合包含扩展(extends)的设计,并且最适合表达对象的“结构”。
type 的优势
而另一方面,type 更具表现力,适合处理联合类型和交叉类型。
1// Good
2type Status = "idle" | "loading" | "success" | "error";
3
4type ApiResponse<T> =
5 | { status: "success"; data: T }
6 | { status: "error"; message: string };type 很适合用于描述状态、选项和组合。
type 和 interface 的选择准则
通常情况下,对象结构和契约用 interface,需要联合、交叉或类型运算等表达能力时用 type。
它们都能实现类似功能,但更重要的是选择能清楚表达类型存在原因的一种。
把你的类型当作文档来对待。
最后,让我们看看“将类型作为文档编写”这一点。
优秀的类型定义能比注释传达更多信息。我们的目标应是“仅通过类型就能理解规范”。
1type ApiError = {
2 code: number;
3 message: string;
4 retryable: boolean;
5};正因如此,TypeScript 的一大优势在于类型定义能够作为一类规范文档。
总结
TypeScript 的最佳实践并不是要过于严格。本质在于通过类型明确代码意图,并编写对变更有韧性的代码。
通过在日常开发中积累这些小规则,长期来看会带来“更容易的代码审查”、“更少的 bug”以及“自己和他人更好地理解代码”等效果。
首先只要思考**“如何用类型去表达?”**,就能写出高质量、TypeScript 风格的代码。
您可以在我们的YouTube频道上使用Visual Studio Code跟随上述文章进行学习。 请也查看我们的YouTube频道。