发布于 2025-03-18 19 阅读
0

我一直在写 TypeScript,但我并不理解它

我承认,我不太懂 TypeScript

前几天,我在处理乐观更新的代码中遇到了一个错误,于是我向同事Filip寻求帮助。Filip 是一位 TypeScript 专家,他提到这个satisfies关键字是我正在寻找的解决方案的一部分。

Satisfies?这到底是什么?为什么我以前从未听说过?我的意思是,我已经使用 TypeScript 有一段时间了,所以我很惊讶我自己都不知道它。

图片描述

此后不久,我偶然发现了 @yacineMTB 发的这条推文,他是X.com (又名 Twitter) 的多产喋喋不休者和工程师:

例如,为什么我不能直接运行TypeScript文件?如果我需要初始化整个目录和项目,那么脚本语言有什么意义呢?

我再次开始疑惑,为什么我之前不知道 TypeScript 的知识。为什么不能运行 TypeScript 文件?脚本语言和编译语言有什么区别?

图片描述

我突然意识到,我并不十分了解我几乎每天都在用的语言的一些基本知识,而我正是用它来创建诸如Open SaaS(一个免费的开源 SaaS 启动器)之类的东西的。

所以我决定退一步,对这些主题进行一些调查。在​​本文中,我将与大家分享我学到的一些最重要的东西。

TypeScript 是什么类型的脚本?

您可能已经听说过 TypeScript 是 JavaScript 的“超集”。这意味着它是 JavaScript 之上的一个附加层,在这种情况下,它允许您向 JavaScript 添加静态类型。

图片描述

有点像 TypeScript 是 JavaScript 的高级版本。或者换句话说,如果 JavaScript 是特斯拉 Model 3 的基础型号,那​​么 TypeScript 就是 Model X Plaid。呜呜呜。

但由于它是JavaScript 的超集,因此它的运行方式实际上与 JavaScript 本身不同。例如,JavaScript 是一种脚本语言,这意味着代码在执行过程中会被逐行解释。它被设计成可以在不同操作系统和硬件配置的 Web 浏览器中运行。这与 C 等低级语言不同,后者需要先编译为特定系统的机器码,然后才能执行。

图片描述

因此,JavaScript 不必先进行编译,而是由 JavaScript 引擎进行解释。另一方面,TypeScript 必须先转换(或“转编译”)为 JavaScript,然后才能由浏览器中的 JavaScript 引擎执行(或作为独立的 NodeJS 应用程序执行)。

这个过程看起来有点像这样:



 → Write TypeScript Code

    → “Transcompile” to JavaScript

      → Interpret JavaScript & Check for Errors

        → JavaScript Engine Compiles and Executes the Code


很有趣,对吧?

但是现在我们已经解决了一些理论问题,让我们继续讨论一些更实际的东西,比如 TypeScript 所熟知的东西:类型


顺便一提…

我们正在Wasp努力创建最好的开源 React/NodeJS 框架,让您快速行动!

这就是为什么我们有现成的全栈应用模板,例如带有 TypeScript 的 ToDo 应用。您所要做的就是安装 Wasp:



curl -sSL https://get.wasp-lang.dev/installer.sh | sh


并运行:



wasp new -t todo-ts


图片描述

您将获得一个开箱即用的带有 Auth 和端到端 TypeSafety 的全栈 ToDo 应用程序,以帮助您学习 TypeScript,或者只是快速安全地开始构建一些东西 :)


玩转satisfies

还记得我向同事寻求帮助时,他的解决方案涉及satisfies关键字吗?为了更好地理解它,我决定打开编辑器并尝试一些基本示例,我发现这是我学到的最有用的东西。

首先,我们以 person 对象为例,将其类型化为Record可以接受一组PossibleKeysstringnumber作为值的 。看起来就像这样:



type PossibleKeys = "id" | "name" | "email" | "age";

const person: Record<PossibleKeys, string | number> = { } 


我们输入常量的方式person称为类型注释。它直接位于变量名之后。

让我们开始向该person对象添加键和值:



type PossibleKeys = "id" | "name" | "email" | "age";

const person: Record<PossibleKeys, string | number> = {
  id: 12,
  name: "Vinny",
  email: "vince@wasp-lang.dev",
  age: 37,
} 


看起来很简单,对吧?

现在,让我们看看 TypeScript 如何推断属性的类型person

图片描述

有趣的是,当我们将鼠标悬停在 上时email,我们会看到 TypeScript 告诉我们 email 是 astring或 a的联合类型number,尽管我们明确地将其定义为 a string

如果我们尝试在此类型上使用某些string方法,这将产生一些意想不到的后果。让我们尝试该split方法,例如:

图片描述

我们收到一个错误,提示该方法对类型 不起作用number。这是正确的。但这很烦人,因为我们知道这email是一个字符串。

让我们satisfies通过将类型移到常量定义的末尾来解决这个问题:



type PossibleKeys = "id" | "name" | "email" | "age";

const person = {
  id: 12,
  name: "Vinny",
  email: "vince@wasp-lang.dev",
  age: 37,
} satisfies Record<PossibleKeys, string | number>;


现在,当将鼠标悬停在email属性上时,我们将看到它被正确推断为string

图片描述

太棒了!现在,我们可以毫无问题地split将其转换email为字符串数组。

这就是satisfies真正的亮点。它让我们验证表达式的类型是否与某个类型匹配,同时推断出最窄的类型。

超额财产检查

但是我在使用过程中注意到的另一个奇怪的事情satisfies是,如果我直接在变量上使用它而不是在中间变量上使用它,它的行为会有所不同,如下所示:



// Directly on object literal
const person = { } satisfies PersonType;

// Using on intermediate variable
const personIntermediate = person satisfies PersonType


具体来说,如果我向对象添加另一个person类型中不存在的属性,比如isAdmin,直接使用时会出错,但使用中间变量则不会出错:

  1. 直接使用satisfies

图片描述

  1. 使用satisfies中间变量

图片描述

您可以看到,在示例 2 中,没有错误并且 person“满足” PersonType,尽管在示例 1 中并非如此。

这是为什么?

嗯,这实际上与 JavaScript 的基本工作原理有关,与satisfies关键字关系不大。让我们来看看。

上述例子中发生的过程就是所谓的“多余财产检查”。

过多的属性检查实际上是规则的例外。TypeScript 使用所谓的“结构类型系统”。这只是一种奇特的说法,即如果一个值具有所有预期的属性,它将被使用。

因此,使用personIntermediate上述示例,TypeScript 不会抱怨具有中不存在的person额外属性。它具有所有其他必要的属性,例如、、和,因此 TypeScript 以这种中间形式接受它。isAdminPersonTypeidnameemailage

但是,当我们直接在变量上声明类型时,就像我们在示例 1 中所做的那样,我们会收到 TypeScript 错误:“'isAdmin' 在类型 'PersonType' 中不存在”。这是过度属性检查在起作用,它可以帮助你避免犯愚蠢的错误。

记住这一点是很好的,因为这将帮助你避免意想不到的副作用。

例如,假设我们将人员类型更改为具有可选isAdmin属性,如下所示:



type PersonType = {
  id: number,
  name: string,
  isAdmin?: boolean, // 👈 Optional
}


person如果我们意外地用isadmin属性isAdmin而不是直接声明类型来定义,会发生什么?

我们不会从 TypeScript 中得到任何错误,因为person实际上确实满足了所有必要的类型。isAdmin类型是可选的,并且它不存在person,但这并不重要。而且您已经做了一个简单的 type-o,现在尝试访问isAdmin属性,但它不起作用:

图片描述

哎呀!让我们用类型注释来修复它,我们立即声明类型:

图片描述

很好。因为我们在第 58 行使用了直接类型注释,所以我们可以获得 TypeScript 的多余属性检查的好处。

谢谢,TypeScript!🙏


如果您发现此内容有用,并且希望看到更多类似的内容,您可以在 GitHub 上给 Wasp 一颗星,从而轻松地帮助我们! 。

https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qgbmn45pia04bxt6zf83.gif

⭐️ GitHub 上的 Star Wasp 🙏


待续…

感谢您加入我的旅程第一部分,以更好地了解我们每天使用的工具。

这将是一系列持续的文章,我将继续以更具探索性、更不具结构性的方式分享我所学的知识。希望您觉得其中的某些部分有用或有趣。

让我知道你接下来想看什么!你喜欢这种风格吗?你会改变一些东西吗?添加或删除一些东西?或者你对最近学到的东西有什么看法或类似的故事吗?

如果是的话,请在评论中告诉我们,下次再见:)