IDE 超能力

IDE 超能力

VS Code 中的 TypeScript 提供了诸如自动补全和错误检查等基本工具,确保了高效和可靠的编码体验。

无论你使用何种 IDE,TypeScript 的工作方式都是相同的,但在本书中我们假设你正在使用 Visual Studio Code (VS Code)。

当你打开 VS Code 时,TypeScript 服务器会在后台启动。只要你打开了 TypeScript 文件,它就会保持活动状态。

让我们来看看 VS Code 中一些由 TypeScript 服务器支持的强大功能。

自动补全

如果要我说出一个我离不开的 TypeScript 功能,那一定是自动补全。

TypeScript 知道你应用中所有东西的类型。因此,它可以在你输入时提供建议------极大地提高了你的速度。

在下面的示例中,仅在 audioElement后输入 'p' 就会显示所有以 'p' 开头的属性。

ini 复制代码
const audioElement = document.createElement("audio");

audioElement.p; // play, pause, part 等

这对于探索你可能不熟悉的 API 非常强大,比如本例中的 HTMLAudioElement API。

手动触发自动补全

你经常会需要手动触发自动补全。在 VS Code 中,键盘快捷键 Ctrl + Space 会显示你正在输入内容的建议列表。

例如,如果你要向元素添加事件监听器,你会看到可用事件的列表:

javascript 复制代码
document.addEventListener(

  "", // 在此处自动补全

);

当光标在引号内时按下 Ctrl + Space 快捷键,会显示可以监听的事件列表:

c 复制代码
DOMContentLoaded

abort

animationcancel

...

如果你想将列表缩小到你感兴趣的事件,可以在按下 Ctrl + Space 之前输入 "drag",这样只会显示相应的事件:

erlang 复制代码
drag

dragend

dragenter

dragleave

...

自动补全是编写 TypeScript 代码的重要工具,并且在 VS Code 中开箱即用。

练习

练习 1:自动补全

这是一个可以触发自动补全的代码示例:

typescript 复制代码
const acceptsObj = (obj: { foo: string; bar: number; baz: boolean }) => {};

acceptsObj({

  // 在这里进行自动补全!

});

练习使用自动补全快捷键在调用 acceptsObj 时填充对象。

解决方案 1:自动补全

当你在对象内部按下 Ctrl + Space 时,你会看到一个基于 MyObj 类型的可能属性列表:

ini 复制代码
bar;

baz;

foo;

当你选择每个属性时,自动补全列表会更新以显示剩余的属性。

错误提示

TypeScript 最著名的特性是它的错误提示。这些是 TypeScript 用来确保你的代码按照你的预期运行的一系列规则。

每次你对文件进行更改时,TypeScript 服务器都会检查你的代码。

如果 TypeScript 服务器发现任何错误,它会通知 VS Code 在有问题的代码部分下方绘制一条红色波浪线。将鼠标悬停在带下划线的代码上会显示错误消息。一旦你做出更改,TypeScript 服务器会再次检查,如果错误已修复,则会移除红色波浪线。

我喜欢把它想象成一位老师在你打字时悬在你肩膀上方,用红笔划出你的作业。

让我们更深入地看看这些错误。

有时 TypeScript 会警告你一些在运行时肯定会失败的事情。

例如,考虑以下代码:

csharp 复制代码
const a = null;

a.toString();
'a' 可能为 'null'。18047

TypeScript 告诉我们 a 存在问题。这告诉我们问题在哪里,但并不一定告诉我们问题是什么。在这种情况下,我们需要停下来思考为什么我们不能在 null 上调用 toString()。如果我们这样做,它将在运行时抛出错误。

javascript 复制代码
Uncaught TypeError: Cannot read properties of null (reading 'toString').

在这里,TypeScript 告诉我们即使我们不需要检查,错误也可能发生。非常方便。

并非 TypeScript 警告我们的所有内容都会在运行时实际失败。

看看这个例子,我们正在给一个空对象分配一个属性:

ini 复制代码
const obj = {};

const result = obj.foo;
属性 'foo' 在类型 '{}' 上不存在。2339

TypeScript 在 foo 下方绘制了一条红色波浪线。但是如果我们仔细想想,这段代码实际上不会在运行时导致错误。我们试图访问这个对象中不存在的属性:foo。这不会报错,只会意味着 resultundefined

TypeScript 警告我们一些不会导致错误的事情,这看起来可能有些奇怪,但实际上是件好事。如果 TypeScript 没有警告我们这一点,那就意味着我们可以在任何时候访问任何对象上的任何属性。在整个应用程序的生命周期中,这可能会导致相当多的错误。

最好将 TypeScript 的规则看作是"有主见的"(opinionated)。它们是一系列有用的提示,可以使你的应用程序整体上更安全。

尽可能靠近问题源的警告

TypeScript 会尝试尽可能在靠近问题源的地方给出警告。

让我们看一个例子。

css 复制代码
type Album = {

  artist: string;

  title: string;

  year: number;

};

const album: Album = {

  artsist: "Television", // 对象字面量只能指定已知的属性,但 'artsist' 在类型 'Album' 中不存在。你是不是想写 'artist'?2561
  title: "Marquee Moon",

  year: 1977,

};

我们定义了一个 'Album' 类型------一个包含三个属性的对象。然后,我们通过 const album: Album 说明 album 常量必须是该类型。如果你现在还不理解所有语法,别担心------我们稍后会全部介绍。

你能看到问题吗?在创建专辑时,artist 属性有一个拼写错误。这是因为我们已经说过 album 变量需要是 Album 类型,但我们将 artist 拼写成了 artsist。TypeScript 告诉我们犯了一个错误,甚至建议了正确的拼写。

然而,有时 TypeScript 会在一个更不友好的地方给出错误。

在这个例子中,我们有一个名为 logUserJobTitle 的函数,它记录用户的职位名称:

ini 复制代码
const logUserJobTitle = (user: {

  job: {

    title: string;

  };

}) => {

  console.log(user.job.title);

};

现在不用担心语法------我们稍后会讲到。重要的是 logUserJobTitle 接受一个用户对象,该对象有一个 job 属性,而 job 属性又有一个 title 属性。

现在,让我们用一个 job.title 是数字而不是字符串的用户对象来调用 logUserJobTitle

arduino 复制代码
const exampleUser = {

  job: {

    title: 123,

  },

};

logUserJobTitle(exampleUser);
类型 '{ job: { title: number; }; }' 的参数不能赋给类型 '{ job: { title: string; }; }' 的参数。
  这些类型之间 'job.title' 的类型不兼容。
    类型 'number' 不能赋给类型 'string'。2345

看起来 TypeScript 应该在 exampleUser 对象中的 title 上给我们一个错误。但相反,它在 exampleUser 变量本身上给出了错误。

这个错误有好几行,会让人觉得很吓人。处理多行错误的一个好经验法则是从底部开始阅读:

arduino 复制代码
类型 'number' 不能赋给类型 'string'。

这告诉我们一个 number 被传递到一个期望是 string 的位置。这是问题的根源。

让我们看下一行:

arduino 复制代码
这些类型之间 'job.title' 的类型不兼容。

这告诉我们 job 对象中的 title 属性是问题所在。

这样,我们甚至不需要看顶行(通常是问题的长篇总结)就能理解问题了。

从下往上阅读错误是处理多行 TypeScript 错误时一个有用的策略。

查看变量和声明信息

你不仅可以悬停在错误消息上。任何时候你将鼠标悬停在变量或声明上,VS Code 都会显示关于它的信息。

在这个例子中,我们可以将鼠标悬停在 thing 上,看到它的类型是 number

ini 复制代码
let thing = 123;
     let thing: number

悬停功能也适用于更复杂的例子。这里 otherObject 展开了 otherThing 的属性,并添加了 thing

ini 复制代码
let otherThing = {

  name: "Alice",

};

const otherObject = {

  ...otherThing,

  thing: "abc",

};

otherObject.thing;

将鼠标悬停在 otherObject 上会显示其所有属性的计算结果:

c 复制代码
console.log(otherObject);
                const otherObject: {
    thing: string;
    name: string;
}

根据你悬停的内容,VS Code 会显示不同的信息。例如,悬停在 otherObject 上会显示其所有属性,而悬停在 thing 上则会显示其类型。

习惯于在代码库中自由查看变量和声明,因为这是理解代码行为的好方法。

练习

练习 1:悬停查看函数调用

在这个代码片段中,我们试图使用 document.getElementById 和 ID 12 来获取一个元素。然而,TypeScript 报错了。

javascript 复制代码
let element = document.getElementById(12);
类型 'number' 的参数不能赋给类型 'string' 的参数。2345

悬停如何帮助确定 document.getElementById 实际需要什么参数?另外,作为一个加分题,element 的类型是什么?

解决方案 1:悬停查看函数调用

首先,我们可以将鼠标悬停在红色波浪线错误本身上。在这种情况下,悬停在 12 上,我们会得到以下错误消息:

arduino 复制代码
类型 'number' 的参数不能赋给类型 'string' 的参数。

我们还会得到 getElementById 函数的说明:

php 复制代码
(method) Document.getElementById(elementId: string): HTMLElement | null

对于 getElementById,我们可以看到它需要一个字符串作为参数,并且返回一个 HTMLElement | null。我们稍后会讨论这种语法,但它基本上意味着返回一个 HTMLElement 或者 null

这告诉我们可以通过将参数更改为字符串来修复错误:

javascript 复制代码
let element = document.getElementById("12");
      let element: HTMLElement | null

我们也知道 element 的类型将是 document.getElementById 返回的类型,我们可以通过悬停在 element 上来确认这一点。

所以,在不同的地方悬停会显示不同的信息。当我在 TypeScript 中工作时,我会不断地悬停鼠标以更好地了解我的代码在做什么。

JSDoc

JSDoc 是一种为代码中的类型和函数添加文档的语法。它允许 VS Code 在悬停时显示的弹出窗口中显示额外的信息。

这在与团队合作时非常有用。

以下是如何为一个 logValues 函数添加文档的示例:

markdown 复制代码
/**

 * 将对象的键值对打印到控制台。

 *

 * @param obj - 要打印的对象。

 *

 * @example

 * \`\`\`ts

 * logValues({ a: 1, b: 2 });

 * // 输出:

 * // a: 1

 * // b: 2

 * \`\`\`

 */

const logValues = (obj: any) => {

  for (const key in obj) {

    console.log(\`${key}: ${obj[key]}\`);

  }

};

@param 标签用于描述函数的参数。@example 标签用于提供函数使用方法的示例,使用 markdown 语法。

JSDoc 注释中还有许多可用的标签。你可以在 JSDoc 文档 中找到它们的完整列表。

无论你是在开发库、团队协作还是个人项目,添加 JSDoc 注释都是一种传达代码目的和用法的好方法。

练习

练习 1:为悬停提示添加文档

这是一个将两个数字相加的简单函数:

typescript 复制代码
const myFunction = (a: number, b: number) => {

  return a + b;

};

为了理解这个函数的作用,你必须阅读代码。

为函数添加一些文档,以便当你将鼠标悬停在它上面时,可以阅读到它的功能描述。

解决方案 1:为悬停提示添加文档

在这种情况下,我们将指明该函数"将两个数字相加"。我们还可以使用 @example 标签来显示函数使用方法的示例:

typescript 复制代码
/**

 * 将两个数字相加。

 * @example

 * myFunction(1, 2);

 * // 将返回 3

 */

const myFunction = (a: number, b: number) => {

  return a + b;

};

现在,每当你将鼠标悬停在此函数上时,除了函数签名外,还会显示注释以及 @example 标签下内容的完整语法高亮:

typescript 复制代码
// 悬停在 myFunction 上显示:
const myFunction: (a: number, b: number) => number

将两个数字相加。

@example
myFunction(1, 2);
// 将返回 3

虽然这个例子很简单(我们当然可以给函数起个更好的名字),但这对于记录你的代码来说是一个极其重要的工具。

使用"跳转到定义"和"跳转到引用"进行导航

TypeScript 服务器还提供了导航到变量或声明定义的功能。

在 VS Code 中,这个"跳转到定义"的快捷方式在 Mac 上是 Command + 单击,在 Windows 上是 F12(针对当前光标位置)。你也可以在任一平台上右键单击并从上下文菜单中选择"跳转到定义"。为简洁起见,我们将使用 Mac 的快捷方式。

到达定义位置后,重复 Command + 单击 快捷方式将显示该变量或声明被使用的所有位置。这被称为"跳转到引用"。这对于在大型代码库中导航特别有用。

该快捷方式也可用于查找内置类型和库的类型定义。例如,如果你在使用 getElementById 方法时对 document 执行 Command + 单击,你将被带到 document 本身的类型定义。

这是理解内置类型和库如何工作的一个很棒的功能。

重命名符号

在某些情况下,你可能希望在整个代码库中重命名一个变量。假设一个数据库列从 'id' 更改为 'entityId'。简单的查找和替换将不起作用,因为 'id' 在许多地方用于不同的目的。

一个名为"重命名符号"的 TypeScript 支持的功能允许你通过单个操作来完成此操作。

让我们看一个例子。

ini 复制代码
const filterUsersById = (id: string) => {

  return users.filter((user) => user.id === id);

};

右键单击 filterUsersById 函数的 id 参数,然后选择"重命名符号"。

将显示一个面板,提示输入新名称。输入 userIdToFilterBy 并按 Enter。VS Code 足够智能,能够意识到我们只想重命名函数的 id 参数,而不是 user.id 属性:

ini 复制代码
const filterUsersById = (userIdToFilterBy: string) => {

  return users.filter((user) => user.id === userIdToFilterBy);

};

重命名符号功能是重构代码的一个很好的工具,并且它也支持跨多个文件工作。

自动导入

大型 JavaScript 应用程序可能由许多模块组成。手动从其他文件导入可能很繁琐。幸运的是,TypeScript 支持自动导入。

当你开始输入要导入的变量名称时,使用 Ctrl + Space 快捷键,TypeScript 会显示一个建议列表。从列表中选择一个变量,TypeScript 会自动在文件顶部添加一个导入语句。

在名称中间使用自动补全时,你需要稍微小心一点,因为该行的其余部分可能会被无意更改。为避免此问题,请确保在按 Ctrl + Space 之前光标位于名称的末尾。

快速修复

VS Code 还提供了一个"快速修复"功能,可用于运行快速重构脚本。现在,让我们用它来同时导入多个缺失的导入。

要打开快速修复菜单,请按 Command + .。如果你在一行引用了尚未导入的值的代码上执行此操作,则会显示一个弹出窗口。

arduino 复制代码
const triangle = new Triangle();
找不到名称 'Triangle'。你是指 'triangle' 吗?2552

快速修复菜单中的一个选项将是"添加所有缺失的导入"。选择此选项会将所有缺失的导入添加到文件的顶部。

javascript 复制代码
import { Triangle } from "./shapes";

const triangle = new Triangle();

我们将在练习中再次查看快速修复菜单。它为重构代码提供了许多选项,并且是了解 TypeScript 功能的好方法。

重启 VS Code 服务器

我们已经看过了 TypeScript 在 VS Code 中可以为你做的几件很酷的事情的例子。然而,运行语言服务器并非易事。TypeScript 服务器有时可能会进入不良状态并停止正常工作。如果更改了配置文件或处理特别大的代码库,则可能会发生这种情况。

如果你遇到奇怪的行为,最好重新启动 TypeScript 服务器。为此,请使用 Shift + Command + P 打开 VS Code 命令面板,然后搜索"Restart TS Server"(重启 TS 服务器)。

几秒钟后,服务器应该会重新启动并确保错误报告正常。

在 JavaScript 中工作

如果你是 JavaScript 用户,你可能已经注意到许多这些功能在不使用 TypeScript 的情况下已经可用。自动补全、组织导入、自动导入和悬停提示在 JavaScript 中都有效。为什么会这样呢?

这是因为 TypeScript。TypeScript 的 IDE 服务器不仅在 TypeScript 文件上运行,也在 JavaScript 文件上运行。这意味着 TypeScript 一些出色的 IDE 体验在 JavaScript 中也可用。

有些功能在 JavaScript 中并非开箱即用。最突出的是 IDE 内错误提示。没有类型注解,TypeScript 对代码的结构不够自信,无法给出准确的警告。

提示:有一种使用 TypeScript 支持的 JSDoc 注释为 .js 文件添加类型的方法,但默认情况下未启用。我们稍后将学习如何配置它。

TypeScript 这样做首先是为了给 VS Code 用户在 JavaScript 中工作提供更好的体验。TypeScript 的一部分功能总比完全没有要好。

但结果是,对于 JavaScript 用户来说,迁移到 TypeScript 应该感觉非常熟悉。这是相同的 IDE 体验,只是更好。

练习

练习 1:快速修复重构

让我们回顾一下我们之前看过的 VS Code 的快速修复菜单。

在这个例子中,我们有一个函数,其中包含一个 randomPercentage 变量,该变量通过调用 Math.random() 并将结果转换为固定数字来创建:

ini 复制代码
const func = () => {

  // 将此重构为其自己的函数
  const randomPercentage = \`${(Math.random() * 100).toFixed(2)}%\`;

  console.log(randomPercentage);

};

这里的目标是将生成随机百分比的逻辑重构为一个独立的函数。

选中一个变量、一行或整个代码块,然后按 Command + . 打开快速修复菜单。根据打开菜单时光标的位置,会有几个修改代码的选项。

尝试不同的选项,看看它们如何影响示例函数。

解决方案 1:快速修复重构

快速修复菜单会根据你打开它时光标的位置显示不同的重构选项。

内联变量

如果你选中 randomPercentage,可以选择一个"内联变量"选项。

这会移除该变量并将其值内联到 console.log 中:

javascript 复制代码
const func = () => {

  console.log(\`${(Math.random() * 100).toFixed(2)}%\`);

};
提取常量

当选择像 Math.random() * 100 这样较小的代码部分时,会出现"提取到闭包作用域中的常量"选项。

选择此选项会创建一个新的局部变量,系统会提示你命名该变量,并将所选值赋给它。保存并运行代码格式化程序后,一切都会被很好地清理:

ini 复制代码
const func = () => {

  const randomTimes100 = Math.random() * 100;

  const randomPercentage = \`${randomTimes100.toFixed(2)}%\`;

  console.log(randomPercentage);

};

类似地,"提取到模块作用域中的常量"选项会在模块作用域中创建一个新的常量:

ini 复制代码
const randomTimes100 = Math.random() * 100;

const func = () => {

  const randomPercentage = \`${randomTimes100.toFixed(2)}%\`;

  console.log(randomPercentage);

};
内联和提取函数

选择整个随机百分比逻辑会启用其他一些提取选项。

"提取到模块作用域中的函数"选项的作用类似于常量选项,但会创建一个函数:

javascript 复制代码
const func = () => {

  const randomPercentage = getRandomPercentage();

  console.log(randomPercentage);

};

function getRandomPercentage() {

  return \`${(Math.random() * 100).toFixed(2)}%\`;

}

这些只是快速修复菜单提供的一些选项。你可以用它们实现很多功能,而我们仅仅触及了皮毛。继续探索和实验,以发现它们的全部潜力!

相关推荐
jonjia2 小时前
对象类型
typescript
jonjia2 小时前
快速搭建 TypeScript 开发环境
typescript
jonjia2 小时前
TypeScript 的奇怪之处
typescript
jonjia2 小时前
类型派生
typescript
jonjia2 小时前
开发流程中的 TypeScript
typescript
jonjia2 小时前
设计你的类型
typescript
jonjia2 小时前
Total TypeScript 精要
typescript
牛奶1 天前
ts随笔:面向对象与高级类型
前端·面试·typescript
SuperEugene1 天前
接口类型管理:从 any 到有组织的 api.d.ts
前端·面试·typescript