前端代码质量保障:ESLint、Prettier和TypeScript的完美结合⚙️

2,204 阅读19分钟

前端代码质量为何如此重要

我这里收集了两篇很有趣的文章可以从反面论证这个问题如何把前端项目写成一座屎山?能把队友气死的8种屎山代码(React版), 可以概述为以下问题:

问题特征示例
组件膨胀组件变得非常庞大,包含大量的逻辑和功能,违反了单一职责原则。一个组件既包含UI渲染逻辑,又包含数据处理、网络请求等功能。
不合理的状态管理状态管理不合理,数据流混乱,难以理解和调试。全局滥用props传递或未合理使用状态管理工具(如ReduxVuex)。
未优化的渲染组件频繁重新渲染,性能下降。render方法中执行昂贵的计算或使用不必要的forceUpdate
过于复杂的条件渲染复杂的条件渲染逻辑,难以理解。使用大量的嵌套if语句或三元表达式来根据不同条件渲染内容。
缺乏组件复用性组件过于专用,难以在不同部分重用。在多个地方复制粘贴相似的代码而不提取为可重用的组件。
未优化的数据传递不合理的数据传递方式,导致不必要的props传递和组件更新。将大量的数据通过props传递给子组件,即使子组件只需要其中一部分数据。
忽视性能优化未考虑性能优化,导致页面加载缓慢或响应时间长。没有使用懒加载、缓存数据或合理使用虚拟列表等性能优化技术。
不合理的路由管理路由管理混乱,没有明确定义的路由结构。未使用路由懒加载,导致首次加载时加载所有路由组件。
硬编码的数据和字符串数据和字符串硬编码在组件中,难以维护和修改。直接在组件中写死API端点或文本内容。
不合理的错误处理错误处理不完善,可能导致未处理的异常和应用崩溃。未使用try...catch块捕获异常,或者忽略了异步操作的错误。
滥用全局状态过度使用全局状态,导致数据管理复杂化。将所有状态都存储在全局状态管理中,而不是在局部组件内部。
无序的文件和目录结构项目缺乏良好的文件和目录组织,难以找到所需的代码文件。未将组件、工具函数和样式表分门别类存放。
不合理的依赖管理依赖项过多或不合理,导致项目庞大和加载时间延长。引入大量不必要的第三方库或组件。

ESLintPrettierTypeScript的作用

ESLintPrettierTypeScript都是前端开发中常用的工具和技术,它们各自具有不同的作用,可以帮助提高代码质量、可维护性和可读性。

  • ESLint

    • 作用ESLint是一款静态代码分析工具,用于检查和识别JavaScript代码中的潜在问题和不良实践。它允许开发者定义一组规则,以强制代码风格、可维护性和性能最佳实践。
    • 用途ESLint可以用来检查代码中的语法错误、代码风格问题、不推荐使用的特性、未使用的变量等,以确保代码质量一致并减少潜在的错误。
  • Prettier

    • 作用Prettier是一个代码格式化工具,它能够自动格式化代码,以确保代码在整个项目中具有一致的风格和格式。
    • 用途Prettier可以格式化代码的缩进、行宽、空格、括号等方面,减少了代码审查过程中关于格式的争议,同时提高了代码的可读性。
  • TypeScript

    • 作用TypeScript是一种静态类型的超集,它为JavaScript添加了类型系统。开发者可以定义变量、函数和对象的类型,并在编译时进行类型检查。
    • 用途TypeScript可以帮助开发者在编码过程中捕获类型错误,提高代码的健壮性和可维护性。它还提供了更强大的IDE支持,使代码更易于理解和维护。

ESLint

安装与配置

假设你已经阅读了前面的文章安装好了node,只需执行下面命令即可安装eslint

# npm
npm install eslint --save-dev
​
# yarn
yarn add eslint -D

初始化配置.eslintrc.js文件

npx eslint --init

接下来是一些规则配置的引导,可以按照自己的代码风格进行填写:

image-20231009214545767.png

当当,一份基础的病历,哦不,配置项就生成好了。

image-20231009214904006.png

通常来说,这样一份配置已经基本能满足日常使用的要求了,但是,本着不折腾折腾要死的精神接下来细细评味每个配置项的作用以及如何选择适合你的规则集。

配置文件概览

module.exports = {
    "env": {
        "browser": true,
        "es2021": true
    },
    "extends": [
        "eslint:recommended",
        "plugin:@typescript-eslint/recommended",
        "plugin:react/recommended"
    ],
    "overrides": [
        {
            "env": {
                "node": true
            },
            "files": [
                ".eslintrc.{js,cjs}"
            ],
            "parserOptions": {
                "sourceType": "script"
            }
        }
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "ecmaVersion": "latest",
        "sourceType": "module"
    },
    "plugins": [
        "@typescript-eslint",
        "react"
    ],
    "rules": {
        "indent": [
            "error",
            "tab"
        ],
        "linebreak-style": [
            "error",
            "windows"
        ],
        "quotes": [
            "error",
            "single"
        ],
        "semi": [
            "error",
            "always"
        ]
    }
}

运行时(env

项目的运行环境,比如在浏览器,node,使用es6es5等等。

设定运行环境的好处,这样能让eslint理解当前处于哪个运行时,从而减少对运行时全局变量误报。如浏览器环境中的window变量和node环境的global

解析器

parserparserOptionsESLint 配置中与代码解析相关的两个重要配置项。它们用于定义如何解析和理解你的 JavaScript 代码。以下是对这两个配置项的详细介绍:

parser 配置项

parser 配置项用于指定用于解析 JavaScript 代码的解析器。解析器负责将代码转换成抽象语法树(AST),以便 ESLint 可以在 AST 上进行规则检查。

常见的解析器选项包括:

  • "espree":这是 ESLint 默认使用的解析器,它支持 ECMAScript 5.1 语法。
  • "babel-eslint":用于支持最新的 ECMAScript 版本和 Babel 扩展的解析器。
  • "@typescript-eslint/parser":用于解析TypeScript 代码。
  • "vue-eslint-parser":用于解析 Vue.js 单文件组件中的代码。

parserOptions 配置项

parserOptions 配置项是parser的补充,它允许你根据自身项目特点进行更深入解析定制。

常见的 parserOptions 选项包括:

  • ecmaVersion:指定要支持的 ECMAScript 版本,如 5、6、2015、2017 等。
  • sourceType:指定模块系统类型,可以是 "script"(默认)或 "module"
  • ecmaFeatures:一个对象,用于启用或禁用特定的语言特性,如 jsx(用于支持 React JSX 语法)。

全局配置

global

globals选项用于定义在代码中全局范围内可访问的变量,而不会引发"未定义的变量"错误。这对于那些在整个项目中都可用的全局变量非常有用,比如浏览器环境中的window对象或Node.js环境中的process对象。

例如,如果你的项目中使用了全局变量myGlobalVar,你可以将其添加到ESLint配置文件中:

// .eslintrc.js
module.exports = {
  // 其他配置选项...
  globals: {
    myGlobalVar: 'readonly', // 可以使用myGlobalVar变量,但不允许重新赋值
  },
};
​

定制化配置

settings

settings选项用于传递一些特定于项目的配置信息给ESLint。这可以包括自定义规则的配置、环境配置、插件配置等。

例如,如果你在项目中使用了React,并且想要启用与React相关的ESLint规则,可以在settings中进行配置:

// .eslintrc.js
module.exports = {
  // 其他配置选项...
  settings: {
    react: {
      version: 'detect', // 自动检测React版本
    },
  },
};

overrides

overrides 选项允许你为特定的文件、目录或通配符模式定义不同的 ESLint 配置。这在处理不同类型的文件或特定规则集时非常有用。例如,你可以针对测试文件使用不同的规则,或者针对不同的目录使用不同的配置。

下面是一个示例,演示如何在 ESLint 配置中使用 overrides

// .eslintrc.js
module.exports = {
  // 通用配置
  rules: {
    'no-console': 'warn',
  },
  overrides: [
    {
      files: ['**/*.test.js'], // 匹配所有测试文件
      rules: {
        'no-console': 'off', // 关闭 no-console 规则
      },
    },
    {
      files: ['src/**/*.js'], // 匹配 src 目录下的所有 JavaScript 文件
      rules: {
        'custom-rule': 'error', // 启用自定义规则
      },
    },
  ],
};
​

ignorePatterns

ignorePatterns 选项允许你指定哪些文件或目录应该被 ESLint 忽略,不进行代码检查。这通常用于排除不需要进行代码质量检查的文件,如第三方库、构建产物、测试数据等。

下面是一个示例,演示如何在 ESLint 配置中使用 ignorePatterns

// .eslintrc.js
module.exports = {
  // 通用配置
  rules: {
    'no-console': 'warn',
  },
  ignorePatterns: ['node_modules/', 'build/'], // 忽略 node_modules 目录和 build 目录下的文件
};

偷学别人的配置(继承)

extends

extends 选项用于引入外部的 ESLint 配置文件,这些配置文件定义了一组规则,用于检查代码的质量和风格。通常,这些配置文件可以是 ESLint 内置的,也可以是社区维护的或自定义的。

常见的用法是引入预定义的配置文件,如 "eslint:recommended""plugin:react/recommended",以便快速启用一组常见的规则。你还可以创建自己的配置文件,并在项目中重复使用。

// .eslintrc.js
module.exports = {
  extends: 'eslint:recommended', // 使用 ESLint 推荐的规则集
  // 其他配置选项...
};

plugins

plugins 选项通常和extends一起使用,用于引入 ESLint 插件,这些插件包含了额外的规则和功能,用于检查特定的代码模式或框架。ESLint 插件通常由社区维护,并提供了与特定技术栈或项目需求相关的规则。

例如,如果你正在使用 React,你可以使用 eslint-plugin-react 插件来启用与 React 代码相关的规则。首先,需要安装该插件:

// .eslintrc.js
module.exports = {
  plugins: ['react'], // 引入 eslint-plugin-react 插件
  extends: ['eslint:recommended', 'plugin:react/recommended'], // 使用 React 相关规则
  // 其他配置选项...
};
​

规则(rules)

ESLint 中,rules 是一个非常重要的配置选项,它用于定义代码检查规则,以控制代码的质量、风格和最佳实践。通过配置 rules,你可以告诉 ESLint 如何处理各种代码问题,例如禁用不建议使用的语法、强制一致的代码风格或检测潜在的错误。

rules 配置对象中的每个属性都代表一个规则,这些规则通常以字符串或数组的形式定义,具体取决于规则的要求。规则可以有以下几种值:

  • "off"0:关闭规则,不执行检查。
  • "warn"1:启用规则,并将问题报告为警告,但不会阻止代码的执行。
  • "error"2:启用规则,并将问题报告为错误,可能会阻止代码的执行。

下面是一个简单的示例,演示如何配置一些常见的 ESLint 规则:

// .eslintrc.js
module.exports = {
  rules: {
    'no-console': 'warn', // 不允许使用 console,报告为警告
    'indent': ['error', 2], // 强制使用两个空格的缩进
    'quotes': ['error', 'single'], // 强制使用单引号
    'semi': ['error', 'always'], // 强制使用分号
  },
};

Prettier

安装与配置

你可以使用 npmyarn 来安装 Prettier,全局安装或项目本地安装都是可行的。

# npm
npm install --save-dev prettier
​
# yarn
yarn add prettier -D

Prettier 支持通过 .prettierrc 配置文件来定义代码格式化规则。创建一个名为 .prettierrc 的文件并定义你的规则,以下是一个示例:

{
  "printWidth": 80,         // 每行的最大字符数
  "tabWidth": 2,            // 缩进宽度
  "useTabs": false,         // 使用空格而不是制表符进行缩进
  "semi": true,             // 在语句末尾使用分号
  "singleQuote": true,      // 使用单引号而不是双引号
  "trailingComma": "all",   // 多行对象和数组的尾随逗号
  "bracketSpacing": true,   // 对象字面量中的括号之间的空格
  "arrowParens": "always"  // 箭头函数参数周围使用括号
}
​

集成 Prettier 到编辑器

为了方便使用 Prettier,你可以将它集成到你使用的代码编辑器中。以下是一些常见的编辑器插件链接,用于集成 Prettier:

使用 Prettier

一旦安装和配置了 Prettier,你可以在命令行中或通过编辑器插件来格式化代码。以下是一些常见的用法示例:

  • 通过命令行格式化文件:

    npx prettier --write yourfile.js
    
  • 通过编辑器插件快捷键触发格式化。

  • 集成 Prettier 到你的构建工具(如 webpackgulp 等),以在构建过程中自动格式化代码。

Prettier 可以帮助你和你的团队保持一致的代码风格,减少代码审查时的格式问题,并提高代码的可读性。

ESLint配合使用

PrettierESLint 可以很好地搭配使用,以确保你的代码既符合一致的格式,又符合代码质量和最佳实践规则。以下是它们搭配使用时需要注意的问题:

  • 规则冲突问题:

    • PrettierESLint 可能会存在一些规则冲突,例如ESLint 可能要求使用特定的缩进,而 Prettier 会自动格式化成另一种缩进样式。为了解决这个问题,可以使用 eslint-config-prettier 插件来禁用与 Prettier 冲突的 ESLint 规则,如示例中的 'plugin:prettier/recommended' 配置。
  • 避免重复规则:

    • 避免在 ESLintPrettier 中定义相同的规则,因为这可能会导致重复的检查和冲突。通常,你可以将代码格式化的任务留给 Prettier,而将更多关注代码质量和最佳实践的任务留给 ESLint
  • 使用编辑器插件:

    • 配置和使用编辑器插件是 PrettierESLint 结合使用的关键。确保你的编辑器插件正确配置,以在保存文件时自动运行 PrettierESLint
  • 预览变化:

    • 在格式化代码之前,使用编辑器插件或命令行工具预览 Prettier 的格式化变化,以确保你满意最终的格式化结果。
  • 手动修复 ESLint 报告的问题:

    • 虽然 Prettier 负责格式化代码,但有时 ESLint 可能会报告其他代码质量问题,如未使用的变量或潜在的错误。这些问题应该根据 ESLint 的建议进行手动修复。

TypeScript

本文并不是ts的学习指南,因此本小结的主要目标是分析ts使如何帮助我们写出高可维护性代码的。

TypeScript是什么

简单来说tsjs的超集(js包含于ts),在js的基础上扩展了静态类型、面向对象的编程范式(如类、继承、抽象类等)能力,并且ts属于编译型语言,最终会编译js,因此开发者无需担心js迁移到ts的兼容性成本。

# 安装ts
yarn add typescript -D
​
# 初始化ts配置项
npx tsc --init
​
# 编译ts代码
npx tsc

配置tsconfig.json文件

{
  "compilerOptions": {
    "target": "ES6",
    "module": "CommonJS",
    "outDir": "./dist",
    "strict": true,
    "noImplicitAny": true,
    "esModuleInterop": true
  },
  "include": [
    "./src/**/*.ts"
  ],
  "exclude": [
    "node_modules",
    "**/*.test.ts"
  ]
}

这几乎就是日常会用到的所以关于ts的配置,如果需要用到一些特殊的配置,查阅官方文档即可。

  • compilerOptions:这是一个包含许多编译选项的对象。以下是一些常见的配置选项:

    • target:指定要编译为的 ECMAScript 版本,如 "ES3", "ES5", "ES6", "ES2015", "ES2017", 或 "ESNext"。通常,你会设置为 "ES6" 或更高版本,以利用新的语言特性。
    • module:指定生成的模块系统,可以是 "CommonJS", "AMD", "System", "UMD", "ES6", 或 "ESNext"。这取决于你的项目和目标环境。
    • outDir:指定编译后的 JavaScript 文件的输出目录。
    • strict:将一组严格的类型检查选项打开。通常,将其设置为 true 以启用强类型检查。
    • noImplicitAny:禁止隐式的 any 类型。如果设置为 true,则需要明确为每个变量指定类型。
    • esModuleInterop:允许在导入默认导出时使用 import x from 'module' 语法。
  • include:一个包含匹配文件和目录模式的数组,指定哪些文件应该包含在编译中。

  • exclude:一个包含匹配文件和目录模式的数组,指定哪些文件应该排除在编译之外。

类型检查

本文的目的是讲解如何通过类型检查写出优雅的代码而不是教大家如何写类型体操,关于ts类型体操,推荐这篇文章接近天花板的TS类型体操,看懂你就能玩转TS了。言归正传,接下来我将从类型定义、类型推断、如何处理类型报错三个方面讲解ts的类型魔法。

类型定义

类型定义是开发者显式为变量、函数参数、函数返回值等代码元素指定类型的方式。通过类型注解,你告诉 TypeScript 某个变量或参数应该具有什么类型。类型注解使用冒号(:)来定义。

function add(a: number, b: number): number {
    return a + b;
}

在上面的例子中,ab 被显式注解为 number 类型,而函数 add 的返回值也被注解为 number 类型。

类型定义为js注入了静态语言的特性,它能带给开发者更大的掌控感和安全感(类型错误会在编译时抛出),当然也会丢失js部分灵活性和高效性。个人更偏爱ts带来的掌控感和安全感,让我能够更加确信自己的代码bug free

类型推断

类型推断是 TypeScript 编译器自动推断代码中元素的类型的过程,而不需要显式的类型注解。TypeScript使用类型推断来确定变量、函数参数、函数返回值等的类型,基于变量的初始化值和上下文信息。

let x = 42; // TypeScript 推断 x 为 number 类型

在上述例子中,TypeScript 推断 x 的类型为 number,因为它被初始化为数字。

类型推断还在函数参数和返回值中发挥作用:

typescriptCopy codefunction multiply(a: number, b: number) {
    return a * b; // TypeScript 推断返回值类型为 number
}

在这里,虽然没有显式注解函数 multiply 的返回值类型,TypeScript 会根据函数体中的操作自动推断返回值类型为 number

如何处理类型报错

  1. 仔细阅读错误信息: TypeScript 提供了详细的错误信息,包括错误类型、位置和相关上下文。仔细阅读这些错误信息是解决问题的第一步。错误信息通常会告诉你哪里出了问题以及如何修复它。

  2. 检查类型注解: 确保你的类型注解与变量、参数和函数返回值的实际使用方式匹配。如果你在注解和实际值之间存在不匹配,会导致类型错误。

  3. 使用类型断言: 有时候,你可能比 TypeScript 更了解某个值的类型。在这种情况下,你可以使用类型断言(Type Assertion)告诉TypeScript 如何处理该值。但要小心使用,确保你的断言是正确的,否则可能引入运行时错误。

    let value: any = "Hello, TypeScript!";
    let length: number = (value as string).length; // 类型断言
    
  4. 调整变量或函数签名: 如果你发现类型错误是因为变量或函数的签名与实际用途不匹配,你可能需要重新设计或调整它们。

  5. 利用工具和编辑器支持: 当你在代码中处理类型错误时,现代编辑器如 Visual Studio Code 通常会提供代码补全和错误修复建议。利用这些工具来更轻松地纠正错误。

ESLintTypeScript

安装必备依赖

yarn add @typescript-eslint/parser @typescript-eslint/eslint-plugin -D

配置到Eslint配置项中

{
  "parser": "@typescript-eslint/parser",
  "plugins": ["@typescript-eslint"],
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended"
  ]
}

ok,到这里就能在项目中使用ts + eslint + prettier结合体啦,这里附赠一个临时禁用规则的注释(谨慎使用,IDE中可以装插件自动生成禁用注释,不需要刻意记忆)

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const unusedVar: string = "Hello";

代码质量工作流程

提交前检查

Git提供了代码提交过程中的一些生命周期钩子,我们可以借助lint-stagedhusky两个包很方便配置代码静态检查能力。

  • husky 是一个工具,用于管理 Git 钩子。
  • lint-staged 用于在提交前只运行钩子在暂存区的文件上。

配置 lint-stagedpackage.json 文件中,添加一个 lint-staged 配置,用于指定在提交前运行的命令。例如:

"lint-staged": {
  "*.{js,ts}": [
    "eslint --fix",
    "prettier --write",
    "git add"
  ]
}

这个配置表示在提交前,针对 JavaScriptTypeScript 文件,将首先运行 eslint --fix 来修复ESLint 错误,然后运行 prettier --write 来格式化代码,最后使用 git add 添加更改。

配置 husky 钩子:package.json 文件中,添加一个 husky 配置,以在 pre-commit 钩子中运行 lint-staged

"husky": {
  "hooks": {
    "pre-commit": "lint-staged"
  }
}

这个配置会在每次提交前运行 lint-staged

持续集成

使用持续集成工具(如Jenkins)来集成代码质量检查,下面以Jenkins为例讲解集成的过程

  1. 安装 Jenkins 首先,你需要在服务器上安装 Jenkins。按照官方文档的说明来执行这一步骤。
  2. 安装必要的插件: 安装Jenkins 插件,例如 NodeJS 插件,以便在 Jenkins 上执行 Node.js 项目。
  3. 创建Jenkins 任务:Jenkins 上创建一个新任务。你可以选择自由风格项目或流水线项目,具体取决于你的需求。在任务配置中,定义构建步骤,包括安装和配置代码质量检查工具、运行测试和检查代码质量。
  4. 配置触发: 配置 Jenkins 任务的触发条件。你可以选择轮询代码库、Webhooks 或使用插件(如 GitHubGitLab 插件)来触发构建。
  5. 执行构建: 一旦任务配置完成,Jenkins 将根据触发条件自动执行构建。在构建过程中,代码质量检查工具将运行,检查代码的质量。

团队协作

  • 在团队中制定统一的代码规范,并用脚手架存储含有该规范的项目模板。
  • 通过代码审查(提PR)来确保一致性

结语

感谢你能看到最后,这是前端工程化系列的第三篇文章,后续陆续会产出构建工具、缓存管理、兼容性、模块化、性能优化、自动化测试等内容,如果对你有帮助的话点个👍和收藏吧❤️

参考文献

如何把前端项目写成一座屎山?

能把队友气死的8种屎山代码(React版)

接近天花板的TS类型体操,看懂你就能玩转TS了