✨ TypeScript 中的 5 个糟糕想法

我**爱❤️** TypeScript。

尤其是在经历了 JavaScript 臭名昭著的错误之后。

然而,尽管 TypeScript 很棒,但仍然有可能导致错误。

在这篇文章中,我将分享 TypeScript 中的 5 种不良做法以及如何避免它们。

📚 下载我的免费 101 个 React 技巧和窍门书,抢占先机。

Section Divider

1. 将错误声明为 Any 类型

例子

在下面的代码片段中,我们捕获错误,然后将其声明为任何类型。

async function asyncFunction() {
  try {
    const response = await doSomething();
    return response;
  } catch (err: any) {
    toast(`Failed to do something: ${err.message}`);
  }
}

为什么它不好❌

无法保证错误具有字符串类型的消息字段。

不幸的是,由于类型断言,代码让我们假设它确实如此。

代码可以在开发过程中通过特定的测试用例运行,但在生产过程中可能会出现严重故障。

该怎么办?

不要设置错误类型。默认情况下应为“未知”。

相反,您可以执行以下任一操作:

  • 选项 1:使用类型保护检查错误是否属于正确类型。
  • async function asyncFunction() {
      try {
        const response = await doSomething();
        return response;
      } catch (err) {
        const toastMessage = hasMessage(err)
          ? `Failed to do something: ${err.message}`
          : `Failed to do something`;
        toast(toastMessage);
      }
    }
    
    // We use a type guard to check first
    function hasMessage(value: unknown): value is { message: string } {
      return (
        value != null &&
        typeof value === "object" &&
        "message" in value &&
        typeof value.message === "string"
      );
    }
    
    // You can also simply check if the error is an instance of Error
    const toastMessage = err instanceof Error
          ? `Failed to do something: ${err.message}`
          : `Failed to do something`;
  • 选项 2(推荐):不要对错误做出假设
  • 不要对错误类型做出假设,而是明确处理每种类型并向用户提供适当的反馈。

    如果无法确定具体的错误类型,**最好显示完整的错误信息**而不是部分详细信息。

    有关错误处理的更多信息,请查看这本出色的指南:编写更好的错误消息。

    Section Divider

    2. 函数带有多个相同类型的连续参数

    例子

    export function greet(
      firstName: string,
      lastName: string,
      city: string,
      email: string
    ) {
      // Do something...
    }

    为什么它不好❌

  • 您可能会意外地以错误的顺序传递参数:
  • // We inverted firstName and lastName, but TypeScript won't catch this
    greet("Curry", "Stephen", "LA", "stephen.curry@gmail.com")
  • 在函数声明之前看到函数调用时,很难理解每个参数代表什么,尤其是在代码审查期间
  • 该怎么办?

    使用对象参数来阐明每个字段的用途并最大限度地降低出错的风险。

    export function greet(params: {
      firstName: string;
      lastName: string;
      city: string;
      email: string;
    }) {
      // Do something...
    }
    Section Divider

    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:
          // This ensures TypeScript catches unhandled cases
          ((_: never) => {})(animalType);
      }
    }

    为什么它不好❌

  • 添加新的动物类型可能会导致返回结构错误的对象。
  • 返回类型结构的改变可能会导致代码其他部分出现难以追踪的问题。
  • 拼写错误可能会导致推断出错误的类型。
  • 该怎么办?

    明确指定函数的返回类型:

    type Animal = {
      name: string;
      sound: string;
    };
    
    function getAnimalDetails(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:
          // This ensures TypeScript catches unhandled cases
          ((_: never) => {})(animalType);
      }
    }
    Section Divider

    4. 添加不必要的类型而不是使用可选字段

    例子

    type Person = {
        name: string;
        age: number;
    }
    
    type PersonWithAddress = Person & {
        address: string;
    }
    
    type PersonWithAddressAndEmail = PersonWithAddress & {
        email: string;
    }
    
    type PersonWithEmail = Person & {
        email: string;
    }

    为什么它不好❌

  • 无法扩展:添加新字段需要创建多种新类型
  • 使类型检查更加复杂,需要额外的类型保护
  • 导致类型名称混乱,维护困难
  • 该怎么办?

    使用可选字段可以使您的类型保持简单且易于维护:

    type Person = {
      name: string;
      age: number;
      address?: string;
      email?: string;
    };
    Section Divider

    5. 使属性在不同的组件级别上成为可选的

    例子

    在所有组件中,“disabled”属性都是可选的。

    interface TravelFormProps {
        disabled?: boolean;
    }
    
    export function TravelForm(props: TravelFormProps) {
        // Uses the date range picker component...
    }
    
    interface DateRangePickerProps {
      disabled?: boolean;
    }
    
    function DateRangePicker(props: DateRangePickerProps) {
      // Uses the date picker component...
    }
    
    interface DatePickerProps {
      disabled?: boolean;
    }
    
    function DatePicker(props: DatePickerProps) {}

    为什么它不好❌

  • 很容易忘记传递已禁用的属性,导致表单部分启用
  • 该怎么办?

    使共享字段成为内部组件的**必需**。

    这将确保 prop 传递正确。这对于较低级别的组件尤其重要,可以尽早发现任何疏忽。

    在上面的例子中,现在所有内部组件都需要“disabled”。

    interface TravelFormProps {
        disabled?: boolean;
    }
    
    export function TravelForm(props: TravelFormProps) {
        // Uses the date range picker component...
    }
    
    interface DateRangePickerProps {
      disabled: boolean | undefined;
    }
    
    function DateRangePicker(props: DateRangePickerProps) {
      // Uses the date picker component...
    }
    
    interface DatePickerProps {
      disabled: boolean | undefined;
    }
    
    function DatePicker(props: DatePickerProps) {}

    :如果您正在为库设计组件,我不建议这样做,因为必填字段需要更多工作。

    Section Divider

    概括

    TypeScript 很棒,但没有一个工具🛠️是完美的。

    避免这 5 个错误将帮助您编写更干净、更安全、更易于维护的代码。

    欲了解更多提示,请查看我的免费电子书。

    🐞 发现漏洞

    🐞 本周小贴士

    就这样结束了🎉。

    留下评论📩来分享你所犯的一个 Typescript 错误。

    别忘了说一声“💖🦄🔥”。

    如果您正在学习 React,请**免费**下载我的《101 个 React 技巧和窍门》一书。

    如果您喜欢这样的文章,请加入我的**免费**新闻通讯**FrontendJoy**。

    如果您需要每日提示,请在 X/Twitter 或 Bluesky 上找到我。