【TypeScript】TypeScript的类型定义与实际业务常见问题代码的对策

2025-02-10T11:47:43+08:00 | 7分钟阅读 | 更新于 2025-02-10T11:47:43+08:00

Macro Zhao

【TypeScript】TypeScript的类型定义与实际业务常见问题代码的对策

@TOC

推荐超级课程:

引言

虽然设计思想会有所不同,但在使用TypeScript进行开发时,编写类型安全的代码被认为是理想的。然而,在实际业务中,由于时间限制、工数限制或多人协作等因素,我们常常会遇到与理想状态相背离的代码。 例如,为了重用现有的JavaScript代码,我们可能会使用any类型,但实际上我们希望定义明确的类型以便在调用时安全使用。或者,由于现有实现复杂且难以理解,我们可能会暂时将其设为nullable,但实际上我们希望彻底调查并安全使用。以下将介绍在TypeScript开发中常见的此类问题及其对策。

(准备知识)类型推断与类型注解

类型推断是指在声明函数时,如果不明确指出函数的类型,TypeScript会根据return的值来推断函数的返回类型(如果没有return,则可能推断为void类型)。

function getUser() = {
  return {
        name: "Alice",
        age: 30
    };
};
// 返回值类型为{
//    name: string,
//    age: number
//}

类型注解则相反,是在声明函数时明确指定类型。在调用函数时,这与类型推断没有区别,但在编辑函数时,如果返回值违反了注解的类型(或者没有返回),则会发生编译错误。

type User = {
  name: string;
  age: number;
};
function getUser(): User = {
  return {
        name: "Alice",
        age: 30
    };
};
//返回值类型为“User”

注解类型可以提高开发时代码的健壮性,减少错误。而类型推断则可以减少描述量,有其优势。

实际业务中的理想与现实

如上所述,在TypeScript开发中,编写类型安全的代码可以减少错误,对开发者来说是理想的代码。但是,在实际业务中,由于各种限制和妥协,我们常常会遇到偏离理想状态的代码。 以下章节将介绍实际业务中常见的问题及其解决对策。

实际业务中常见问题及对策

函数的返回值因类型推断而变成不必要的联合类型

由于类型推断,有时会产生不必要的联合类型返回值。特别是在函数的返回值类型由类型推断决定时,需要注意return的值。以下是一个例子。

function validate(value: string){
    if(isSameValue(value)){
        return {
            validate: false,
            message: "同じ名前は使えません"
        }
    }
    return {
        validate: true,
        message: ""
    }
}

在这种情况下,validate的类型如下。

{
    validate: boolean;
    message: string;
}

如果我们想为“创建模式”添加一个功能,以跳过同名的检查,我们可能会进行如下修改。

function validate(value: string){
    // 添加
    if(Mode === "Create"){
        return { 
            validate: true
            // 本来想包含message: "",但忘记了
        }
    }
    // 以下略
}

这时,validate的类型变成了以下形式。

{
    validate: boolean;
    message?: undefined;
} | {
    validate: boolean;
    message: string;
}

最初的对象返回值变成了对象的联合类型。这导致在使用validate函数的返回值时,需要考虑message可能是undefined。 本来我们希望在返回值中包含message,但由于类型推断的状态,在函数内部不会发生编译错误,因此很难注意到这个错误。

对策

通过为函数的类型添加注解,可以防止这种错误。如果时间和工数允许,尽量添加注解是一个好习惯。

// 添加
type ValidateResult = {
    validate: boolean;
    message: string;
}
function validate(value: string): ValidateResult{ // 添加
    // 以下略
}

为简单的函数添加专用的注解可能是费时的,特别是在需要修改现有函数且该函数使用类型推断时,所需的工数可能会更大。然而,如果不添加注解,那么要么需要特别注意返回值类型,要么需要使用单元测试等方法。 简单地说,如果不添加注解,上述问题可能会频繁发生。如果有注解,就可以避免一些不必要的确认,否则就需要编写单元测试来验证。特别是在未来功能可能会增加,或者多人共同开发的情况下,注解的使用是非常有益的。

强行共用Props接口导致信息难以追踪

本章节以React/TypeScript为例,探讨了在实际开发中遇到的问题。即使不了解React,也能理解内容。 在将Props用接口强行共通化时,特别是在多人开发的情况下,可能会导致关系变得非常复杂。特别是当多人协作时,由于工作量和影响范围的限制,常常会倾向于先将属性设置为nullable。 例如,假设有一个ProductSummary组件和一个ProductDetail组件,它们都使用Item接口作为Props。目前,两个组件都实现了使用商品名和商品价格的处理。现在,我们想对ProductDetail进行修改,以显示商品说明的文章。

interface Item {
    itemName: string;
    itemPrice: number;
    itemDescription?: string | undefined // 新功能添加
}
function ProductSummary(props: Item) {
    // 使用itemName和itemPrice的处理
}
function ProductDetail(props: Item) {
    // 使用itemName, itemPrice和itemDescription的处理 // 新功能添加
}
function MainPage(itemName, itemPrice) {
    return (
        <ProductSummary props={(itemName, itemPrice)} /> // 在SubPage中也使用,无法轻易将itemDescription设为必填
        <ProductDetail props={(itemName, itemPrice, itemDescription)} />
    )
}
function SubPage(itemName, itemPrice) {
    return <ProductSummary props={(itemName, itemPrice)} />;
}

Item.itemDescription在ProductSummary中是不必要的,但在ProductDetail中是必需的。由于之前的设计,我们直接在Item接口中添加了nullable的itemDescription。 如果重复这种做法,可能会导致不需要的信息传递给组件,或者由于信息在类型上被设置为nullable,导致调用方忘记传递必要的信息,导致运行时无法传递。 这个问题的一种发展形式是,一个类型被应用于父组件和子组件,使得追踪传递了哪些nullable信息变得非常困难。以下是一个例子。

  • ProductSummary有一个子组件ProductDetail,再有一个子组件ProductPreview。
  • 所有组件都接收Item类型的Props,但所需的信息各不相同。
interface Item {
    itemName: string;
    itemPrice: number;
    itemDescription?: string | undefined;
    itemImage?: Image | undefined; // 新功能添加
}
function ProductSummary(props: Item) {
    // 使用itemName和itemPrice的处理
    ProductDetail({
        itemName: "猪肉",
        itemPrice: 100,
        itemDescription: "猪肉",
    })
}
function ProductDetail(props: Item) {
    // 使用itemName, itemPrice和itemDescription的处理
    ProductPreview(props)
}
function ProductPreview(props: Item) {
    // 使用itemName, itemPrice, itemDescription和itemImage的处理 // 新功能添加
}

在ProductPreview中,我们想要使用Item.itemImage的信息,但运行时似乎是undefined。由于它是nullable,没有编译错误,所以我们需要在实现中追踪它。 ProductDetail调用了ProductPreview,看起来没有问题。但是在ProductSummary中调用了ProductDetail,那里没有传递itemImage的信息。 如果不遵循这样的步骤,就无法追踪Item.itemImage的值在哪里发生了变化。在这个例子中,通过跟踪两次调用就可以发现,但在多人开发的大型项目中,这个问题可能会变得混乱,导致难以确定原因。 因此,调查和修正的范围扩大,结果问题往往被搁置。

对策

为了解决这个问题,可以为每个组件准备专用的接口。这样就可以抑制不必要的nullable属性,确保在调用时传递真正必要的信息。

interface Item {
    // 省略相同的部分
}
// 新创建
interface ProductDetailItem {
    itemName: string;
    itemPrice: number;
    itemDescription: string;
    itemImage: Image; 
}
function ProductSummary(props: Item) {
    // 使用itemName和itemPrice的处理
    ProductDetail({
        itemName: "猪肉",
        itemPrice: 100,
        itemDescription: "猪肉",
        // ProductDetailItem.itemImage是必需的,所以这里会出错
    })
}
function ProductDetail(props: ProductDetailItem) {
    // 使用itemName, itemPrice, itemDescription和item
    ProductPreview(props)
}
// 以下略

为ProductDetail准备专用接口后,可以将itemDescription和itemImage设置为non nullable(必填)。这样,如果不传递调用时所需的信息,就会产生错误。 今后,即使新设函数或需要新的属性,也可以采取同样的应对措施,从而降低信息过多或过少的调查成本,使开发变得更加可行。

结语

在业务过程中,从开发者的角度来看,虽然我们希望不惜成本编写高质量的代码,但由于交货期限、工数限制或现有规格的关系,往往不得不做出妥协。这样的情况我认为是非常多的。 本次提出的例子,是笔者在遇到的问题中,特别想要坚守的一些原则。我也希望各位能够参考这篇文章,研究出“适合自己的理想TypeScript编码方式”。 感谢您阅读到最后。

© 2011 - 2025 Macro Zhao的分享站

关于我

如遇到加载502错误,请尝试刷新😄

Hi,欢迎访问 Macro Zhao 的博客。Macro Zhao(或 Macro)是我在互联网上经常使用的名字。

我是一个热衷于技术探索和分享的IT工程师,在这里我会记录分享一些关于技术、工作和生活上的事情。

我的CSDN博客:
https://macro-zhao.blog.csdn.net/

欢迎你通过评论或者邮件与我交流。
Mail Me

推荐好玩(You'll Like)
  • AI 动·画
    • 这是一款有趣·免费的能让您画的画中的角色动起来的AI工具。
    • 支持几十种动作生成。
我的项目(My Projects)
  • 爱学习网

  • 小乙日语App

    • 这是一个帮助日语学习者学习日语的App。
      (当然初衷也是为了自用😄)
    • 界面干净,简洁,漂亮!
    • 其中包含 N1 + N2 的全部单词和语法。
    • 不需注册,更不需要订阅!完全免费!
  • 小乙日文阅读器

    • 词汇不够?照样能读日语名著!
    • 越读积累越多,积跬步致千里!
    • 哪里不会点哪里!妈妈再也不担心我读不了原版读物了!
赞助我(Sponsor Me)

如果你喜欢我的作品或者发现它们对你有所帮助,可以考虑给我买一杯咖啡 ☕️。这将激励我在未来创作和分享更多的项目和技术。🦾

👉 请我喝一杯咖啡

If you like my works or find them helpful, please consider buying me a cup of coffee ☕️. It inspires me to create and share more projects in the future. 🦾

👉 Buy me a coffee