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

2025-05-28

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

我承认,我不太了解 TypeScript

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

Satisfies这到底是什么?我以前怎么没听说过?我是说,我已经用 TypeScript 一段时间了,所以很惊讶自己竟然不知道。

图片描述

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

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

我又一次开始疑惑,为什么我之前对 TypeScript 一无所知。为什么不能运行 TypeScript 文件?脚本语言和编译型语言之间有什么区别?

图片描述

我突然意识到,我并不十分了解我几乎每天都在使用的语言的一些基本知识,而我正是用这种语言来创建诸如Open SaaS之类的东西的,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


Enter fullscreen mode Exit fullscreen mode

很有趣吧?

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


顺便一提…

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

因此,我们提供了现成的全栈应用模板,例如使用 TypeScript 的 ToDo 应用。您只需安装 Wasp 即可:



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


Enter fullscreen mode Exit fullscreen mode

并运行:



wasp new -t todo-ts


Enter fullscreen mode Exit fullscreen mode

图片描述

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


玩转satisfies

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

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



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

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


Enter fullscreen mode Exit fullscreen mode

我们为常量添加类型的方式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,
} 


Enter fullscreen mode Exit fullscreen mode

看起来很简单,对吧?

现在,让我们看看 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>;


Enter fullscreen mode Exit fullscreen mode

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

图片描述

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

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

超额财产检查

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



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

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


Enter fullscreen mode Exit fullscreen mode

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

  1. 直接使用satisfies

图片描述

  1. 使用satisfies中间变量

图片描述

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

这是为什么?

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

上述示例中发生的过程就是所谓的“多余属性检查”。

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

因此,使用personIntermediate上面的例子,TypeScript 不会抱怨它person有一个额外的属性,isAdmin而这个属性在 中并不存在PersonType。它拥有所有其他必要的属性,例如idnameemailage,因此 TypeScript 会以这种中间形式接受它。

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

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

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



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


Enter fullscreen mode Exit fullscreen mode

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

TypeScript 不会报错,因为person实际上它满足所有必需的类型。该isAdmin类型是可选的,并且不存在person,但这没关系。你犯了一个简单的错误,现在尝试访问该isAdmin属性,但它不起作用:

图片描述

哎呀!让我们用类型注释来解决这个问题,我们直接声明类型:

图片描述

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

谢谢,TypeScript!🙏


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

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

⭐️ GitHub 上的 Star Wasp 🙏


待续…

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

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

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

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

文章来源:https://dev.to/wasp/ive-been-writing-typescript-without-understanding-it-5ef4
PREV
🎉 我们的 Web 框架在 GitHub 上获得了 9,000 颗星!⭐️ 9️⃣0️⃣0️⃣0️⃣ ⭐️
NEXT
如何在 2024 年获得 Web 开发人员的工作(不会内心消亡)🧑‍💻💀 嘿,我是文斯...... 2024 年开发人员的当前就业市场成为问题解决者,而不仅仅是程序员解决自己的问题做繁重的工作做一个好人将更多的精力投入到更少的应用程序中现在就得到那份工作……