ESLint 和 TypeScript 之间的区别

像 ESLint 这样的代码检查工具和像 TypeScript 这样的类型检查工具可以捕捉代码中不同字段的缺陷,并且最好将它们结合使用。

如果你今天是一名 JavaScript 开发者,很有可能你正在使用 ESLint 和 TypeScript 的组合来辅助开发。这些工具执行类似但不同的功能。ESLint 是一个代码检查器,而 TypeScript 是一个类型检查器

🌐 If you’re a JavaScript developer today, there’s a good chance you’re using a combination of ESLint and TypeScript to assist development. These tools perform similar but different functions. ESLint is a linter, whereas TypeScript is a type checker.

代码检查工具和类型检查器是两种静态分析工具,它们分析代码并报告检测到的问题。虽然它们乍一看似乎相似,但代码检查工具和类型检查器检测不同类别的问题,并在不同方面具有不同的作用。

🌐 Linters and type checkers are two kinds of static analysis tooling that analyze code and report on detected issues. While they may seem similar at first, linters and type checkers detect different categories of issues and are useful in different ways.

要理解这些差异,首先有助于了解什么是静态分析以及它为什么有用。

🌐 To understand these differences, it’s first helpful to understand what static analysis is and why it’s useful.

什么是静态分析?

🌐 What is static analysis?

静态分析是对源代码的检查,而无需执行它。这与动态分析不同,动态分析是在执行源代码的同时进行检查。因此,动态分析带来了执行恶意代码或产生副作用的固有风险,而静态分析无论源代码如何都可以安全执行。

🌐 Static analysis is the inspection of source code without executing it. This differs from dynamic analysis, in which source code is inspected while it is executed. As such, dynamic analysis brings with it the inherent danger of executing malicious code or creating side effects while static analysis is safe to execute regardless of the source code.

静态分析对于提高代码的可读性、可靠性和整体质量非常有帮助。许多开发者依赖静态分析来强制一致的代码格式和风格,以确保代码有良好的文档,并捕捉可能的错误。因为静态分析在源代码上运行,它可以在代码编写过程中在编辑器中提供改进建议。

🌐 Static analysis can be immensely helpful for improving code readability, reliability, and overall quality. Many developers rely on static analysis to enforce consistent code formatting and style, to ensure code is well-documented, and to catch likely bugs. Because static analysis runs on source code, it can suggest improvements in editors as code is written.

ESLint 的静态分析被组织为一系列单独配置的 lint 规则。由于没有两个规则会相互作用,你可以根据自己的偏好安全地打开或关闭每条规则。虽然 TypeScript 有一些单独配置的选项,但大多数分析是在类型检查功能中执行的。

🌐 ESLint’s static analysis is organized as a series of individually configured lint rules. Because no two rules interact with one another, you can safely turn each rule on and off depending on your preferences. While TypeScript has some individually configured options, the majority of the analysis is performed in the type checking functionality.

ESLint 和 TypeScript 使用一些相同的分析形式来检测代码中的缺陷。它们都会分析作用域和变量在代码中的创建和使用情况,并且可以捕捉诸如引用不存在的变量等问题。我们将探索两者使用分析代码信息的不同方式。

🌐 ESLint and TypeScript use some of the same forms of analysis to detect defects in code. They both analyze how scopes and variables are created and used in code, and can catch issues such as referencing a variable that doesn’t exist. We’ll explore the different ways the two use information from analyzing your code.

深入探讨代码风格检查与类型检查

🌐 Digging deeper into linting vs. type checking

代码检查工具主要报告可能的缺陷,也可以用于执行主观意见。ESLint和其他代码检查工具可以捕捉可能或不可能类型安全的问题,但它们是潜在的错误来源。许多开发者依赖代码检查工具来确保他们的代码遵循框架和语言的最佳实践。

🌐 Linters primarily report likely defects and can also be used to enforce subjective opinions. ESLint and other linters catch issues that may or may not be type-safe but are potential sources of bugs. Many developers rely on linters to make sure their code follows framework and language best practices.

例如,开发者有时会在 switch 语句的 case 末尾省略 breakreturn。 这样做是类型安全的,并且 JavaScript 和 TypeScript 允许这样写。 在实际操作中,这几乎总是一个错误,会导致下一个 case 语句意外执行。 ESLint 的 no-fallthrough 可以发现这个可能的错误:

🌐 For example, developers sometimes leave out the break or return at the end of a switch statement’s case. Doing so is type-safe and permitted by JavaScript and TypeScript. In practice, this is almost always a mistake that allows the next case statement to run accidentally. ESLint’s no-fallthrough can catch that likely mistake:

function logFruit(value: "apple" | "banana" | "cherry") {
    switch (value) {
        case "apple":
            console.log("🍏");
            break;

        case "banana":
            console.log("🍌");

        // eslint(no-fallthrough):
        // Expected a 'break' statement before 'case'.

        case "cherry":
            console.log("🍒");
            break;
    }
}

// Logs:
// 🍌
// 🍒
logFruit("banana");

另一方面,类型检查器确保值只以其类型允许的方式使用。编译型语言,如 Java,会在编译阶段执行类型检查。因为 JavaScript 没有办法指示绑定的预期类型,它无法自行执行类型检查。这就是 TypeScript 的作用所在。

🌐 Type checkers, on the other hand, ensure that values are used only in ways that are allowed by the value’s type. Compiled languages, like Java, perform type checking during the compilation phase. Because JavaScript has no way to indicate the intended type of a binding, it cannot perform type checking on its own. That’s where TypeScript comes in.

通过允许显式类型注释(以及某些类型的隐式检测),TypeScript 在 JavaScript 代码之上覆盖类型信息,以执行类似于编译型语言中的类型检查。 例如,TypeScript 会在以下 logUppercase(9001) 调用中报告类型错误,因为 logUppercase 声明为接收 string 而不是 number

🌐 By allowing explicit type annotations (and the implicit detection of some types), TypeScript overlays type information on top of JavaScript code to perform type checking similar to what’s found in compiled languages. For example, TypeScript reports a type error on the following logUppercase(9001) call, because logUppercase is declared to receive a string rather than a number:

function logUppercase(text: string) {
    console.log(text.toUpperCase());
}

logUppercase(9001);
//           ~~~~
// Argument of type 'number' is not assignable to parameter of type 'string'.

TypeScript 专注于报告已知错误,而不是潜在问题;TypeScript 报告的错误没有任何主观性,也没有办法实现特定项目的偏好设置。

🌐 TypeScript focuses on reporting known errors rather than potential problems; there is nothing subjective about the errors that TypeScript reports, nor is there a way to implement project-specific preferences.

另一种看待 ESLint 和 TypeScript 之间差异的方式是,TypeScript 强制规定你可以做什么,而 ESLint 强制规定你应该做什么。

🌐 Another way of looking at the differences between ESLint and TypeScript is that TypeScript enforces what you can do, whereas ESLint enforces what you should do.

细粒度可扩展性

🌐 Granular extensibility

ESLint 和 TypeScript 之间的另一个区别在于配置的粒度。

🌐 Another difference between ESLint and TypeScript is in granularity of configuration.

ESLint 运行时会使用一组可以单独配置的 lint 规则。如果你不喜欢某个特定的 lint 规则,可以针对一行、一组文件或整个项目将其关闭。 ESLint 还可以通过 插件 来增强,这些插件会添加新的 lint 规则。 特定插件的 lint 规则扩展了 ESLint 配置可以选择的代码检查范围。

🌐 ESLint runs with a set of individually configurable lint rules. If you don’t like a particular lint rule, you can turn it off for a line, set of files, or your entire project. ESLint can also be augmented by plugins that add new lint rules. Plugin-specific lint rules extend the breadth of code checks that ESLint configurations can pick and choose from.

例如,这个 ESLint 配置启用了 eslint-plugin-jsx-a11y 推荐的规则,这是一个为使用 JSX 库(如 Solid.js 和 React)的项目添加可访问性检查的插件:

🌐 For example, this ESLint configuration enables the recommended rules from eslint-plugin-jsx-a11y, a plugin that adds checks for accessibility in projects using JSX libraries such as Solid.js and React:

import js from "@eslint/js";
import jsxA11y from "eslint-plugin-jsx-a11y"

export default [
    js.configs.recommended,
    jsxA11y.flatConfigs.recommended,
    // ...
];

一个使用 JSX 可访问性规则的项目将会被告知其代码是否违反了常见的可访问性指南。 例如,渲染一个没有描述性文本的原生 <img> 标签将会收到来自 jsx-a11y/alt-text 的报告:

🌐 A project using the JSX accessibility rules would then be told if their code violates common accessibility guidelines. For example, rendering a native <img> tag without descriptive text would receive a report from jsx-a11y/alt-text:

const MyComponent = () => <img src="source.webp" />;
//                        ~~~~~~~~~~~~~~~~~~~~~~~~~
// eslint(jsx-a11y/alt-text):
// img elements must have an alt prop, either with meaningful text, or an empty string for decorative images.

通过添加插件中的规则,ESLint 配置可以根据项目所使用框架的具体最佳实践和常见问题进行定制。

🌐 By adding in rules from plugins, ESLint configurations can be tailored to the specific best practices and common issues to the frameworks a project is built with.

另一方面,TypeScript 是通过项目级别的一组固定编译器选项来配置的。tsconfig.json 文件 允许你设置编译器选项,从而改变项目中所有文件的类型检查。这些编译器选项在 TypeScript 中是全局设置的,并且通常会改变大范围的类型检查行为。TypeScript 不允许在单个项目的不同文件之间使用不同的编译器选项。

🌐 TypeScript, on the other hand, is configured by a set list of compiler options on a project level. The tsconfig.json file allows you to set compiler options that change type checking for all files in the project. Those compiler options are set globally for TypeScript and generally change large swathes of type checking behavior. TypeScript does not allow compiler options to be different across different files in a single project.

重叠区域

🌐 Areas of overlap

虽然 ESLint 和 TypeScript 的操作方式不同,并且专注于代码缺陷的不同字段,但它们之间存在一些重叠。某些类型的代码缺陷处于“最佳实践”和“类型安全”的交界处,因此可以被两者工具检测到。

🌐 While ESLint and TypeScript operate differently and specialize in different areas of code defects, there is some overlap. Specific types of code defects straddle the line between “best practices” and “type safety,” and so can be caught by both tools.

我们建议在你的 TypeScript 项目中同时使用 ESLint 和 TypeScript,以确保你能够发现最多数量和类型的缺陷。以下是一些帮助你入门的步骤:

🌐 We recommend using both ESLint and TypeScript in your TypeScript projects to ensure you’re catching the widest number and types of defects. Here are a few steps to get you started:

注意: typescript-eslint 的 tseslint.configs.recommended 会禁用对 TypeScript 没有帮助的核心 ESLint 规则。该配置保留对类型检查有用的任何核心 ESLint 规则。

未使用的本地变量和参数

🌐 Unused locals and parameters

在使用 linting 时,我们建议保持关闭的唯一 TypeScript 编译器选项是那些启用未使用变量检查的选项:

🌐 The only TypeScript compiler options we recommend keeping off when using linting are those that enable checking for unused variables:

当不使用 ESLint 时,这些编译器选项是有用的。 然而,它们不像 lint 规则那样可配置,因此无法根据项目的偏好配置为更高或更低的严格级别。 例如,编译器选项被硬编码为始终忽略任何以 _ 开头的变量,而 ESLint 的 no-unused-vars 在未配置时不会以任何不同的方式对待这些变量。

🌐 These compiler options are useful when not using ESLint. However, they aren’t configurable the way lint rules are, and so can’t be configured to higher or lower strictness levels based on a project’s preferences. For example, the compiler options are hardcoded to always ignore any variable whose name begins with _ while ESLint’s no-unused-vars doesn’t treat these variables any differently until configured to do so.

举例来说,以下 registerCallback 函数为其回调声明了两个参数,idmessage,但使用它的开发者只需要 message。 TypeScript 的 noUnusedParameters 编译器选项不会标记未使用的参数 _

🌐 As an example, the following registerCallback function declares two parameters for its callbacks, id, and message, but the developer using it only needed message. TypeScript’s noUnusedParameters compiler option would not flag the unused parameter _:

type Callback = (id: string, message: string) => void;

declare function registerCallback(callback: Callback): void;

// We only want to log message, not id
registerCallback((_, message) => console.log(message));

JavaScript 中未使用的变量也可以通过 ESLint 的 no-unused-vars 规则来捕获;在 TypeScript 代码中,优先使用 @typescript-eslint/no-unused-vars。 可以配置 lint 规则以忽略名称以 _ 开头的变量。

🌐 Unused variables in JavaScript can also be caught by ESLint’s no-unused-vars rule; when in TypeScript code, the @typescript-eslint/no-unused-vars is preferable instead. The lint rules can be configured to ignore variables whose name begins with _.

此外,默认情况下,lint 规则会忽略出现在任何被使用参数之前的参数。一些项目更倾向于无论名称或位置如何,都不允许出现未使用的参数。这些更严格的偏好有助于防止 API 设计导致开发者创建许多未使用的参数。

🌐 Additionally, the lint rules by default ignore parameters that come before any parameter that is itself used. Some projects prefer to never allow unused parameters regardless of name or position. These stricter preferences help prevent API designs that lead developers to create many unused parameters.

一个更严格的 ESLint 配置将能够报告 _ 参数:

🌐 A more strict ESLint configuration would be able to report on the _ parameter:

/* eslint @typescript-eslint/no-unused-vars: ["error", { "args": "all", "argsIgnorePattern": "" }] */

type Callback = (id: string, message: string) => void;

declare function registerCallback(callback: Callback): void;

// We only want to log message, not id
registerCallback((_, message) => console.log(message));
//                ~
// eslint(@typescript-eslint/no-unused-vars):
// '_' is declared but never used.

no-unused-vars 规则提供的额外可配置性层级,使它们能够作为其相应 TypeScript 编译器选项的更细粒度的可配置版本。

🌐 That extra level of configurability provided by the no-unused-vars rules allows them to act as more granularly configurable versions of their equivalent TypeScript compiler options.

💡 查看 no-unused-binary-expressions: 从代码审查的小问题到生态系统改进 了解更多代码检查的字段,这些字段在 linting 和类型检查之间有部分重叠。

ESLint 对 TypeScript 有用吗?

🌐 Is ESLint useful with TypeScript?

是的。

如果你正在使用 TypeScript,使用 ESLint 仍然非常有用。事实上,当 ESLint 和 TypeScript 结合使用时,它们的功能最强大。

🌐 If you are using TypeScript, it is still very useful to use ESLint. In fact, ESLint and TypeScript are at their most powerful when used in conjunction with each other.

带类型信息的 ESLint

🌐 ESLint with type information

传统的 ESLint 规则一次只运行在单个文件上,并且不了解项目中的其他文件。它们无法根据其他文件的内容对文件做出决策。

🌐 Traditional ESLint rules run on a single file at a time and have no knowledge of other files in the project. They can’t make decisions on files based on the contents of other files.

然而,如果你的项目使用 TypeScript 设置,你可以选择使用“类型检查”的 lint 规则:这些规则可以获取类型信息。使用这些规则时,类型检查的 lint 规则可以根据其他文件做出决策。

🌐 However, if your project is set up using TypeScript, you can opt into “type checked” lint rules: rules that can pull in type information. In doing so, type checked lint rules can make decisions based on other files.

例如,@typescript-eslint/no-for-in-array 能够检测针对数组类型值的 for...in 循环,即使这些值来自其他文件。TypeScript 不会对数组上的 for...in 循环报告类型错误,因为从技术上讲这样做是类型安全的,并且可能正是开发者想要的。然而,lint 工具可以被配置为注意到开发者可能犯了一个错误,原本可能打算使用 for...of 循环:

🌐 For example, @typescript-eslint/no-for-in-array is able to detect for...in loops over values that are array types, even if the values come from other files. TypeScript would not report a type error for a for...in loop over an array because doing so is technically type-safe and might be what a developer intended. A linter, however, could be configured to notice that the developer probably made a mistake and meant to use a for...of loop instead:

// declare function getArrayOfNames(): string[];
import { getArrayOfNames } from "./my-names";

for (const name in getArrayOfNames()) {
    // eslint(@typescript-eslint/no-for-in-array):
    // For-in loops over arrays skips holes, returns indices as strings,
    // and may visit the prototype chain or other enumerable properties.
    // Use a more robust iteration method such as for-of or array.forEach instead.
    console.log(name);
}

类型化 linting 的代价是将 linting 的速度降低到大约类型检查的速度,但它提供了更强大的 lint 规则集。有关使用 typescript-eslint 进行类型化 linting 的更多详细信息,请参阅 Typed Linting: The Most Powerful TypeScript Linting Ever

🌐 Typed linting comes at the cost of slowing down linting to roughly the speed of type-checking, but makes available a more powerful set of lint rules. See Typed Linting: The Most Powerful TypeScript Linting Ever for more details on typed linting with typescript-eslint.

带有代码检查的 TypeScript

🌐 TypeScript with linting

TypeScript 为 JavaScript 增添了额外的复杂性。 这种复杂性通常是值得的,但任何增加的复杂性都可能带来被误用的风险。 ESLint 对于阻止开发者在代码中犯 TypeScript 特有的错误非常有用。

🌐 TypeScript adds extra complexity to JavaScript. That complexity is often worth it, but any added complexity brings with it the potential for misuse. ESLint is useful for stopping developers from making TypeScript-specific blunders in code.

例如,TypeScript 的 {}(“空对象”)类型经常被 TypeScript 新手开发者误用。 它看起来像是应该表示任何 object,但实际上表示任何非 null、非 undefined 的值——包括诸如 numberstring 这样的原始类型。 @typescript-eslint/no-empty-object-type 会检测 {} 类型的使用,这些使用很可能本想用 objectunknown

🌐 For example, TypeScript’s {} (“empty object”) type is often misused by developers new to TypeScript. It visually looks like it should mean any object, but actually means any non-null, non-undefined value — including primitives such as numbers and strings. @typescript-eslint/no-empty-object-type catches uses of the {} type that likely meant object or unknown instead:

export function logObjectEntries(value: {}) {
    //                                  ~~
    // eslint(@typescript-eslint/no-empty-object-type):
    // The `{}` ("empty object") type allows any non-nullish value, including literals like `0` and `""`.
    // - If that's what you want, disable this lint rule with an inline comment or configure the 'allowObjectTypes' rule option.
    // - If you want a type meaning "any object", you probably want `object` instead.
    // - If you want a type meaning "any value", you probably want `unknown` instead.
    console.log(Object.entries(value));
}

logObjectEntries(0); // No type error!

使用 ESLint 强制执行特定语言的最佳实践有助于开发者学习和正确使用 TypeScript。

🌐 Enforcing language-specific best practices with ESLint helps developers learn about and correctly use TypeScript.

结论

🌐 Conclusion

像 ESLint 这样的代码检查工具和像 TypeScript 这样的类型检查工具对开发者来说都是宝贵的工具。两者捕捉的代码缺陷字段不同,并且在可配置性和可扩展性方面有不同的理念。

🌐 Linters such as ESLint and type checkers such as TypeScript are both valuable assets for developers. The two catch different areas of code defects and come with different philosophies around configurability and extensibility.

  • ESLint 检查代码是否遵循最佳实践并保持一致性,强制执行你应该写的内容。
  • TypeScript 检查代码是否“类型安全”,强制规范你可以写的内容。

总的来说,这两个工具帮助项目编写出更少错误且更一致的代码。我们建议任何使用 TypeScript 的项目同时使用 ESLint。

🌐 Put together, the two tools help projects write code with fewer bugs and more consistency. We recommend that any project that uses TypeScript additionally uses ESLint.

最新的 ESLint 新闻、案例研究、教程和资源。

ESLint v10.3.0 发布
1 min read

ESLint v10.3.0 发布

我们刚刚发布了 ESLint v10.3.0,这是 ESLint 的一次小版本升级。此版本添加了一些新功能,并修复了上一版本中发现的几个错误。

ESLint v10.2.1 发布
1 min read

ESLint v10.2.1 发布

我们刚刚发布了 ESLint v10.2.1,这是 ESLint 的一个补丁版本升级。本次发布修复了上一版本中发现的几个错误。

ESLint v10.2.0 发布
2 min read

ESLint v10.2.0 发布

我们刚刚发布了 ESLint v10.2.0,这是 ESLint 的一次小版本升级。此版本添加了一些新功能,并修复了上一版本中发现的几个错误。