打字稿运行时类型检查
#javascript #typescript #typesafety #advanced

介绍

打字稿是JavaScript发生的最好的事情,因为很棒的回调金字塔崩溃了。

我们有类型的类型,自动完成,编译时间问题,世界终于处于和平状态,直到某人决定使用instanceof(是的,是我)。

您看到的,即使我们得到了上述所有的好东西,仍然有一些打字稿类型系统的怪癖可能会在雷达下飞行,因为它们经常出现,尤其是当您编写相对简单的应用程序时。

在本文中,我想深入研究对象的主题。具体而言,可以实例化对象的不同方式,它们为何重要以及如何使用 /滥用instanceof运算符。< / p>

我会以一个有关错误处理的实践示例来支持所有内容,并向您展示如何获得打字稿的结构化打字系统的灵活性,并具有运行时对象类型检查的功能。


示例用例

您是前端开发人员,您被要求实现应用程序的错误处理。对于此应用程序,假设您需要处理的错误类型为:

  • 身份验证错误
  • 后端错误
  • 任何不适合前两种类型的未接收错误。

让我们看看如何为此实施一些东西。

1.如果我们只使用打字稿怎么办?

就像一个好的开发人员一样,我们通过为每个错误创建一种类型来开始简单:

type AuthError = { reason: string };

type BackendError = { reason: string };

type UnknownError = { reason: string };

就在大门时,我们有一些主要问题:

  • 对于打字稿,这些都是the same thing
  • 到JavaScript,这些甚至不存在
  • 代码重复

让我们继续前进。

2.如果我们使用课程怎么办?

好主意!毕竟,在解决第二期的运行时也可以上课。

class AuthError {
    constructor(public reason: string) {}
}

class BackendError {
    constructor(public reason: string) {}
}

class UnknownError {
    constructor(public reason: string) {}
}

我们仍然有两个问题。对于打字稿来说,这些仍然是同一件事,我们仍在重复自己。

代码重复问题足够简单,无法处理。我们可以做一个超级阶级,并让其他人扩展它:

class CustomError {
    constructor(public reason: string) {}
}

class AuthError extends CustomError {}

class BackendError extends CustomError {}

class UnknownError extends CustomError {}

上面的代码是完全有效的。尽管打字稿无法分辨出差异,但也许我们不需要它!毕竟,我们可以使用instanceof,以下代码效果很好:

function handleError(error: CustomError) {
    if (error instanceof AuthError) {
        // do something
    } else if (error instanceof BackendError) {
        // do something
    } else if (error instanceof UnknownError) {
        // do something
    }
}

太好了!现在,我们照顾了我们之前提到的所有问题,但是文章尚未完成。哦,哦。即将在JavaScript中改变您对某些事物的看法。

这个问题隐藏在instanceof的工作原理中。

用最简单的话来说,如果someObject是由SomeClass构造器创建或继承的,则someObject instanceof SomeClass将返回True,这意味着他们的某个地方必须有一个new关键字。大事,那呢?我们只会像这样提出错误:

throw new AuthError('Session is expired or something');

好的。足够公平,除了这是JavaScript的狂野未驯服的土地,您也可以在这里这样做:

throw { reason: 'Session is expired or something' };

零警告。零错误。但这甚至不是最糟糕的部分。您刚刚失去了在运行时检查此问题的能力:

const errorObj: AuthError = { reason: 'Session is expired or something' };

console.log(errorObj instanceof AuthError); // -> False

instanceof不使用new关键字的实例化对象时不起作用。

尽管这是一个不太可能的问题,但我个人对instanceof失去了信心。每当在代码的这一区域中发生问题时,我脑海中的一点声音都会不断告诉我“如果是因为您一次读过的那个罕见的错误怎么办?”

我们需要一种不同的方法。一个使我们从JavaScript的怪癖中拯救出来,并允许打字稿真正发光。

3.再次回到类型

引入标签!标签只是一个具有恒定值的属性,该属性放置在类型中,使我们可以在编译时间和运行时自信地缩小给定对象的类型。

这是我们现在可以定义错误的方法:

type AuthError = {
    reason: string,
    errorType: 'auth-error', // <- tag
};

type BackendError = {
    reason: string,
    errorType: 'backend-error', // <- tag
};

type UnknownError = {
    reason: string,
    errorType: 'unknown-error', // <- tag
};

type CustomError = AuthError | BackendError | UnknownError;

有趣的是,CustomError现在位于我们的层次结构的底部,而不是顶部。使用|(Union)操作员,我们告诉Typescript,CustomError是任何给定类型之一。我们能够缩小使用标签的范围。

以下是可以重新编写handleError函数的方式:

function handleError(error: CustomError) {
    switch (error.errorType) {
        case 'auth-error':
            // ...
        case 'backend-error':
            // ...
        default:
            // ...
    }
}

最酷的部分? Typescript正在帮助您沿着每个步骤,并完全了解您要处理的错误类型。

Typescript infers the narrowed types of the errors since we provided a union with the same key

请注意,要为此,标签需要具有所有类型的同名(上面的示例中的errorType)。

如果您对如何以这种方式打印类型感兴趣(使用// ^?)查看this extension(我没有附属,请觉得很棒)。

现在,即使我们将其生育并直接扔对象也无关紧要。打字稿会发疯,迫使我们添加一个标签:

// ERROR: Property 'errorType' is missing in type '{ reason: string; }' but required in type 'AuthError'.
const errorObject: AuthError = {
    reason: string;
};

throw errorObject;

结论

总而言之,本文强调了使用错误处理示例解释的Typescript和JavaScript类型系统的重要性。

通过逐步探索,我们发现使用标签是一个强大而可靠的解决方案。此方法利用了Typescript的结构化键入系统,并允许运行时对象类型检查,从而产生更健壮和可维护的代码。

通过掌握这些细微差别,我们可以在避免语言陷阱的同时充分利用打字稿的潜力。

,要对Typescript进行更多了解,我强烈建议Dan Vanderkam的有效打字稿。