前端工程规范:从代码风格到架构约束的体系化实践

一、规范的困境:有规范不等于被执行

前端团队几乎都有代码规范文档,但规范执行率普遍低于 50%。原因有三:第一,规范文档是静态的,代码是动态的,文档更新永远滞后于实践变化;第二,人工 Code Review 检查规范的成本太高,Review 者更关注逻辑正确性而非风格一致性;第三,规范缺乏强制力,违反规范不会导致构建失败,只是"建议"而非"约束"。

有效的工程规范不是文档,而是自动化工具链。ESLint 检查代码风格,Prettier 格式化代码,TypeScript 约束类型,Husky + lint-staged 在提交前拦截问题。规范的价值不在于"写了什么",而在于"自动执行了什么"。

二、前端工程规范的分层模型

工程规范分为四个层次,从外到内约束力递增:格式规范(自动修复)、风格规范(自动检测)、架构规范(编译时约束)、业务规范(运行时约束)。

graph TB
    subgraph 规范层次模型
        L1[格式规范 Prettier 自动修复]
        L2[风格规范 ESLint 自动检测]
        L3[架构规范 TypeScript 编译约束]
        L4[业务规范 自定义规则 运行时约束]
    end

    L1 --> L2 --> L3 --> L4

    ENFORCE[强制执行点] --> PRE_COMMIT[pre-commit lint-staged]
    ENFORCE --> CI[CI Pipeline 检查]
    ENFORCE --> REVIEW[Code Review 人工]

    L1 -.-> PRE_COMMIT
    L2 -.-> PRE_COMMIT
    L2 -.-> CI
    L3 -.-> CI
    L4 -.-> REVIEW

格式规范由 Prettier 在保存时自动修复,开发者无感知。风格规范由 ESLint 在提交前检查,不通过则无法提交。架构规范由 TypeScript 编译器在 CI 中强制检查。业务规范无法完全自动化,需要 Code Review 人工保障。

三、前端工程规范的自动化配置实践

// ========== package.json — 规范工具链配置 ==========
{
  "scripts": {
    "lint": "eslint . --ext .ts,.tsx --max-warnings 0",
    "lint:fix": "eslint . --ext .ts,.tsx --fix",
    "format": "prettier --write 'src/**/*.{ts,tsx,css,md}'",
    "typecheck": "tsc --noEmit",
    "prepare": "husky"
  },
  "lint-staged": {
    "*.{ts,tsx}": [
      "eslint --fix --max-warnings 0",
      "prettier --write"
    ],
    "*.{css,md,json}": [
      "prettier --write"
    ]
  },
  "devDependencies": {
    "eslint": "^9.0.0",
    "@typescript-eslint/eslint-plugin": "^7.0.0",
    "@typescript-eslint/parser": "^7.0.0",
    "eslint-plugin-react-hooks": "^4.6.0",
    "prettier": "^3.2.0",
    "husky": "^9.0.0",
    "lint-staged": "^15.0.0"
  }
}
// ========== eslint.config.ts — ESLint Flat Config ==========

import tseslint from "@typescript-eslint/eslint-plugin";
import tsparser from "@typescript-eslint/parser";
import reactHooks from "eslint-plugin-react-hooks";

export default [
  {
    files: ["**/*.{ts,tsx}"],
    languageOptions: {
      parser: tsparser,
      parserOptions: {
        project: "./tsconfig.json",
        ecmaFeatures: { jsx: true },
      },
    },
    plugins: {
      "@typescript-eslint": tseslint,
      "react-hooks": reactHooks,
    },
    rules: {
      // === 风格规范 ===
      "no-console": ["warn", { allow: ["warn", "error"] }],
      "no-debugger": "error",
      "prefer-const": "error",
      "no-var": "error",

      // === TypeScript 严格规范 ===
      "@typescript-eslint/no-explicit-any": "error",
      "@typescript-eslint/no-non-null-assertion": "warn",
      "@typescript-eslint/explicit-function-return-type": ["warn", {
        allowExpressions: true,
        allowTypedFunctionExpressions: true,
      }],
      "@typescript-eslint/consistent-type-imports": ["error", {
        prefer: "type-imports",
      }],

      // === React Hooks 规范 ===
      "react-hooks/rules-of-hooks": "error",
      "react-hooks/exhaustive-deps": "warn",

      // === 架构约束规范 ===
      // 禁止直接导入内部模块(强制通过公共 API)
      "no-restricted-imports": ["error", {
        patterns: [
          {
            group: ["**/internal/*"],
            message: "禁止导入 internal 目录,请使用公共 API",
          },
          {
            group: ["**/components/*/styles"],
            message: "禁止跨组件导入样式,请使用 CSS Modules 或设计令牌",
          },
        ],
      }],
    },
  },
];
// ========== 架构约束:TypeScript 严格模式 ==========
// tsconfig.json

{
  "compilerOptions": {
    "strict": true,                    // 启用所有严格检查
    "noUncheckedIndexedAccess": true,  // 数组/对象索引可能 undefined
    "noImplicitOverride": true,        // 必须显式声明 override
    "exactOptionalPropertyTypes": true, // 可选属性不允许赋 undefined
    "paths": {
      "@/*": ["./src/*"],
      "@components/*": ["./src/components/*"],
    }
  }
}
# ========== Husky + lint-staged:提交前自动检查 ==========
# .husky/pre-commit

npx lint-staged
// ========== 业务规范:自定义 ESLint 规则 ==========
// 禁止在组件中直接调用 API(强制通过 Hook)

const noDirectApiCallInComponent = {
  meta: {
    type: "problem" as const,
    docs: {
      description: "组件中禁止直接调用 API,请使用自定义 Hook",
    },
    messages: {
      noDirectApi:
        "组件中直接调用 API 违反架构规范,请将 API 调用封装为 useXxx Hook",
    },
  },
  create(context: any) {
    return {
      // 检测函数组件中的 API 调用
      CallExpression(node: any) {
        const callee = node.callee;
        if (
          callee.type === "Identifier" &&
          /^(fetch|axios|api)\w*/.test(callee.name)
        ) {
          // 判断是否在组件函数内
          const funcParent = context.getAncestors().find(
            (a: any) => a.type === "FunctionDeclaration" ||
                        a.type === "ArrowFunctionExpression"
          );
          if (funcParent && isReactComponent(funcParent)) {
            context.report({
              node,
              messageId: "noDirectApi",
            });
          }
        }
      },
    };
  },
};

四、工程规范的 Trade-offs 分析

规范严格度与开发效率:过于严格的规范会降低开发效率。no-explicit-any: error 在快速原型阶段可能过于严格,频繁的类型标注拖慢开发节奏。建议区分"开发阶段"和"提交阶段":开发时允许宽松,提交时强制严格。

lint-staged 的延迟:每次提交前运行 ESLint 和 Prettier 增加了 3-10 秒的等待时间。对于频繁提交的开发者,这个延迟会累积。建议只对暂存文件(staged files)执行检查,而非全量扫描。

自定义规则的维护成本:自定义 ESLint 规则需要编写 AST 遍历逻辑,开发和维护成本较高。如果规则只在少数场景触发,Code Review 可能比自定义规则更经济。建议只对高频违反的规则做自动化,低频规则靠 Review 保障。

规范与创新的冲突:严格的架构约束(如禁止直接 API 调用)可能限制技术探索。在 Hackathon 或原型验证阶段,需要临时关闭部分规范。建议提供 eslint-disable 的审批流程,而非完全禁止。

五、总结

有效的工程规范不是文档而是自动化工具链。四层模型(格式→风格→架构→业务)从外到内约束力递增,Prettier、ESLint、TypeScript 和自定义规则分别负责不同层次的约束。Husky + lint-staged 在提交前强制执行,CI Pipeline 在合并前二次检查。落地时需要平衡规范严格度与开发效率,自定义规则只覆盖高频违反场景。规范的目标是减少低级错误和风格不一致,而非限制技术探索。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐