ESLint + Prettier + Husky + lint-staged:建立自动化的高效前端工作流

作者:互联网

2026-03-22

Javascript教程

前言

在团队协作中,代码规范往往是一个容易引发争议却又不得不解决的问题。每个人都有自己的编码习惯,有人喜欢加分号,有人不喜欢;有人用两个空格缩进,有人用四个;有人变量命名用 camelCase,有人用 snake_case。这些差异在 Code Review 时往往演变成无休止的争论,消耗着团队的宝贵时间。

这就是为什么要建立自动化代码规范工作流——让工具做工具擅长的事,让人做人擅长的事。

为什么需要自动化代码规范?

没有规范带来的问题

// 团队成员A写的代码
function fetchData(){
let result = getData()
return result
}

// 团队成员B写的代码
function fetchData() {
  const result = getData();
  return result;
}

上述两段代码看起来差不多,但:

  1. 格式不一致(缩进、空格、分号)
  2. 变量命名风格不同
  3. Code Review 时会争论这些细节

有了自动化工具之后

// 不管我们怎么写,保存时自动变成统一格式
function fetchData() {
  const result = getData()
  return result
}
// 提交时自动检查,有问题就拦截
// 再也不用手动改格式了

自动化工作流的价值

传统流程:
写代码 → 手动检查 → 提交 → Code Review → 发现问题 → 修改 → 再次提交

自动化流程:
写代码 → 保存时自动格式化 → 提交时自动检查 → 提交成功
                        ↓
                    发现问题自动拦截

收益:
- 减少 90% 的代码风格争论
- 提前发现 70% 的潜在错误
- Code Review 时间缩短 50%
- 新人融入时间减少 60%

工具链全景图

四大工具的分工

ESLint:代码质量检查

  • 发现潜在错误(未定义变量、未使用变量)
  • 强制最佳实践(使用 === 代替 ==)
  • 统一代码风格(但能力有限)

Prettier:代码美容师

  • 统一代码风格(空格、换行、引号)
  • 专注格式化,不做质量检查

Husky:看门人

  • 在 Git 操作时触发脚本
  • 确保提交前代码符合规范

lint-staged:高效助手

  • 只检查即将提交的文件
  • 避免检查整个项目,提高效率

工作流程示意图

开发阶段
┌─────────────────┐
│  VS Code 编辑器 │
│  - 保存时格式化 │
│  - 实时错误提示 │
└────────┬────────┘
         ↓
Git 提交阶段
┌─────────────────┐
│   git commit    │
└────────┬────────┘
         ↓
Husky 触发 pre-commit
┌─────────────────┐
│  执行 lint-staged│
└────────┬────────┘
         ↓
lint-staged 检查暂存区
┌─────────────────┐
│ 1. 运行 ESLint  │
│ 2. 运行 Prettier│
└────────┬────────┘
    有问题?→ 拦截提交
         ↓
    没问题 → 提交成功

ESLint - 代码质量守门员

什么是 ESLint?

ESLint 就像考试时的阅卷老师,专门帮我们找出代码中的"错误"和"不规范":

// 1. 未使用的变量
let unusedVar = '没人用我'  // ESLint: 'unusedVar' is defined but never used

// 2. 未定义的变量
console.log(notDefined)  // ESLint: 'notDefined' is not defined

// 3. 不安全的比较
if (count == 1) {  // ESLint: Expected '===' and instead saw '=='
  // ...
}

// 4. 重复定义
let name = '张三'
let name = '李四'  // ESLint: 'name' is already defined

安装和初始化

# 安装 ESLint
npm install eslint --save-dev

# 初始化配置
npx eslint --init

# 交互式选择:
# - How would you like to use ESLint? → To check syntax and find problems
# - What type of modules does your project use? → JavaScript modules (import/export)
# - Which framework does your project use? → Vue.js
# - Does your project use TypeScript? → Yes
# - Where does your code run? → Browser
# - What format do you want your config file to be in? → JavaScript

Vue 3 + TypeScript 项目的最佳配置

// .eslintrc.js
module.exports = {
  root: true,
  env: {
    browser: true,
    es2021: true,
    node: true,
    'vue/setup-compiler-macros': true
  },
  extends: [
    'eslint:recommended',
    'plugin:vue/vue3-recommended',  // 使用推荐规则
    'plugin:@typescript-eslint/recommended',
    'plugin:prettier/recommended'    // 整合 Prettier
  ],
  parser: 'vue-eslint-parser',
  parserOptions: {
    ecmaVersion: 'latest',
    parser: '@typescript-eslint/parser',
    sourceType: 'module',
    extraFileExtensions: ['.vue']
  },
  plugins: ['vue', '@typescript-eslint'],
  rules: {
    // 关闭与 Prettier 冲突的规则
    'vue/max-attributes-per-line': 'off',
    'vue/singleline-html-element-content-newline': 'off',
    'vue/html-self-closing': 'off',
    
    // 自定义规则
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    
    // Vue 3 推荐
    'vue/multi-word-component-names': 'off',
    'vue/no-v-model-argument': 'off',
    
    // TypeScript
    '@typescript-eslint/no-explicit-any': 'warn',
    '@typescript-eslint/explicit-module-boundary-types': 'off',
    '@typescript-eslint/no-unused-vars': ['error', { 
      argsIgnorePattern: '^_' 
    }]
  },
  globals: {
    defineProps: 'readonly',
    defineEmits: 'readonly',
    defineExpose: 'readonly',
    withDefaults: 'readonly'
  }
}

自定义规则详解

// .eslintrc.js
module.exports = {
  rules: {
    // 规则级别:off(0) 关闭 / warn(1) 警告 / error(2) 错误
    
    // ========== 最佳实践 ==========
    'eqeqeq': ['error', 'always'],  // 必须用 === 和 !==
    'no-eval': 'error',              // 禁止 eval
    'no-implied-eval': 'error',      // 禁止隐式 eval
    'no-with': 'error',              // 禁止 with 语句
    
    // ========== 变量相关 ==========
    'no-unused-vars': ['error', { 
      vars: 'all',                   // 检查所有变量
      args: 'after-used',           // 检查使用后的参数
      ignoreRestSiblings: true      // 忽略剩余参数
    }],
    'no-use-before-define': ['error', { 
      functions: false,              // 函数可以在定义前使用
      classes: true,                // 类不行
      variables: true               // 变量也不行
    }],
    
    // ========== 代码风格 ==========
    // 这些规则会被 Prettier 覆盖,但保留作为参考
    'array-bracket-spacing': ['error', 'never'],  // [1, 2, 3] 而不是 [ 1, 2, 3 ]
    'object-curly-spacing': ['error', 'always'],  // { foo: bar } 而不是 {foo: bar}
    'comma-dangle': ['error', 'never'],           // 不加尾逗号
    'quotes': ['error', 'single'],                // 用单引号
    'semi': ['error', 'never'],                   // 不加分号
    
    // ========== 复杂度控制 ==========
    'max-depth': ['warn', 4],        // 最大嵌套深度不超过4
    'max-params': ['warn', 5],        // 函数参数不超过5个
    'max-statements': ['warn', 30],    // 函数语句不超过30行
    'complexity': ['warn', 10]         // 圈复杂度不超过10
  }
}

在 package.json 中添加脚本

{
  "scripts": {
    "lint": "eslint . --ext .js,.ts,.vue",
    "lint:fix": "eslint . --ext .js,.ts,.vue --fix",
    "lint:src": "eslint src --ext .js,.ts,.vue"
  }
}

Prettier - 代码美容师

什么是 Prettier?

Prettier 是格式化工具,它只有一个任务:把代码变得好看:

// 这是我们写的(乱七八糟)
function   hello(   name   ){
console.log(   `Hello ${   name   }`   )   }

// Prettier 帮我们变成这样
function hello(name) {
  console.log(`Hello ${name}`)
}

安装与基础配置

# 安装 Prettier
npm install --save-dev prettier

# 安装 ESLint 整合插件
npm install --save-dev eslint-config-prettier eslint-plugin-prettier

Prettier 配置文件

// .prettierrc.js
module.exports = {
  // 基础配置
  printWidth: 100,              // 每行最大宽度
  tabWidth: 2,                  // 缩进空格数
  useTabs: false,               // 用空格代替 tab
  semi: false,                  // 不加分号
  singleQuote: true,            // 用单引号
  quoteProps: 'as-needed',      // 对象属性只在必要时加引号
  trailingComma: 'none',        // 不加尾逗号
  bracketSpacing: true,         // 对象括号内加空格 { foo: bar }
  arrowParens: 'always',        // 箭头函数参数总是加括号 (x) => x
  endOfLine: 'auto',            // 自动处理换行符
  
  // Vue 相关
  vueIndentScriptAndStyle: true, // 缩进