研发幸福论:从import顺序看代码的呼吸感

4,222 阅读6分钟

前言

"啪!"刚接手新项目的你兴奋地双击打开项目文件,却被满屏的 import 语句当头一棒——React、antd、工具函数、业务组件像被猫抓乱的毛线团纠缠在一起

// 优化前的灾难现场
import React, { useState } from "react";
import ComponentC from "../components/ComponentC";
import styles from "./index.module.scss";
import testIcon from "@/images/test/testIcon.svg";
import ComponentA from "../components/ComponentA";
import { getMockDataList } from "@/service/testCenter";
import { Spin } from "antd";
import ComponentB from "../components/ComponentB";
import { useEffect } from "react";

此刻的你,是否想起了被祖传代码支配的恐惧?

image.png

痛点直击:混乱import的三大原罪

  1. 定位失焦:在antd组件和业务工具函数之间反复横跳,找依赖像玩"大家来找茬"
  2. 心智负担:每次新增依赖都要纠结放在哪里,代码洁癖者的噩梦
  3. 协作灾难:团队成员各写各的,PR评审变成import顺序辩论赛

破局之道:像整理衣橱般重构import

第一性原理:优秀的代码组织应该像宜家家具说明书——即使新手也能秒懂装配顺序

技术方案三部曲

  1. 规范定义(立规矩)
// 理想中的import层次
// 第一梯队:框架核心
import React, { useState, useEffect } from 'react'

// 第二梯队:三方库
import { Spin } from 'antd'
import { useRouter } from 'react-router-dom'

// 第三梯队:项目模块
import { getMockDataList } from '@/service/testCenter'
import testIcon from "@/images/test/testIcon.svg";


// 第四梯队:同级组件
import Topic from '../components/Topic'
  1. 自动化校验(上手段)

请先安装 eslint-plugin-import 插件,

npm install eslint-plugin-import --save-dev

否则我们配置完 .eslintrc 文件后会发现,终端会报错:

1:1 error Definition for rule 'import/order' was not found

感谢评论区细心的掘友补充指正。


    // .eslintrc 配置精髓
    
    {
      // 在plugins数组的最后添加上“import”,告诉eslint要使用我们
      // 刚刚安装好的eslint-plugin-import插件
      plugins: ["react", "@typescript-eslint", "react-hooks", "import"],
      rules: {
        "import/order": [
          "error",
          {
            "groups": [
              "builtin",   // Node内置
              "external",  // npm包
              "internal",  // 项目内部
              "parent",    // 父目录
              "sibling",   // 同级
              "index"      // 目录索引
            ],
            "pathGroups": [
              {
                "pattern": "react",
                "group": "external",
                "position": "before"
              },
              {
                "pattern": "@/**",  // 给别名路径特殊待遇
                "group": "internal",
                "position": "before"
              }
            ],
            "newlines-between": "always", // 分组间空行
            // 一定要手动修改下方的值,因为pathGroupsExcludedImportTypes
            // 的默认值是["builtin", "external", "object"],
            // 因此,假如我们不重新赋值,那么我们在pathGroups中
            // 定义的有关react的配置,就会被排除(因为它属于external),设置的position: before
            // 并不会生效,我们会发现eslint还是提示我们应该将antd在react之前import
            // 所以再强调一遍,一定要修改pathGroupsExcludedImportTypes的值
            "pathGroupsExcludedImportTypes": ["builtin"],
            "alphabetize": {              // 字母表排序
              "order": "asc",
              "caseInsensitive": true
            }
          }
        ]
      }
    }
  1. 开发流闭环(提体验)

当我们完成前两步的时候,我们会发现,终端里的报错信息一下子就变多了,对于之前缺少管理的文件,我们得一个个手动修复,这实在是太累了。 因此我们可以修改.vscode/settings.json文件

// 终极武器:保存即自动格式化
{
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
  "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"]
}

这将在保存时自动应用 ESLint 的修复,包括 import 排序。

完成修改配置后,每次 Cmd + S 自动获得:

// 如德芙般丝滑的import结构
import React, { useEffect, useState } from 'react'

import { Spin } from 'antd'
import { useRouter } from 'react-router-dom'

import { getMockDataList } from '@/service/testCenter'
import testIcon from "@/images/test/testIcon.svg";

import styles from './index.module.scss'

import ComponentA from "../components/ComponentA";
import ComponentB from "../components/ComponentB";
import ComponentC from "../components/ComponentC";

Vue生态适配指南

对于<script setup>的魔法世界,只需微调分组策略:

"pathGroups": [
  {
    "pattern": "vue*",  // 捕获vue全家桶
    "group": "external",
    "position": "before"
  }
]

优化效果:

<script setup>
import { ref } from 'vue'
import { useStore } from 'vuex'

import { formatCurrency } from '@/libs/utils'

import ProductCard from './ProductCard.vue'
</script>

❗️❗️❗️注意事项❗️❗️❗️

请注意,当你的vue项目是从vue官方文档通过执行

npm create vue@latest

命令创建的,那么你按照本篇博客的方式进行eslintrc.js的文件修改会出现问题,并不能达成期望的效果🙅。

出于篇幅考虑,在本篇博客中,我仅罗列我遇到的一些问题和解决方法,欢迎掘友在评论区分享其他的踩坑经验。

  1. vite要求esm:如果我们的eslintrc.js文件中存在module.exports的写法,那么在运行这个项目时会报错。由于脚手架项目在package.json中设置了"type": "module",这意味着所有.js文件都会被当作ES模块处理。而ESLint的配置文件如果使用.eslintrc.js,并且里面用了CommonJS的语法(比如module.exports),就会导致错误,因为ES模块中不能使用module.exports
    module.exports => export default 
    
  2. .eslintignore 文件已废弃:在最新版的eslint中,不再建议用户通过创建.eslintignore 文件来忽略某些文件,而是期望在 ESLint 配置中添加 ignores 字段来实现对某些文件的忽略。
        ignores: ["**/node_modules/**", "dist/**", "public/**"], // 替代 .eslintignore
    
  3. ESLint在v8.23.0之后引入了Flat Config格式:新的配置文件方式,使用eslint.config.js,而传统的.eslintrc.*文件不再被支持。
    // eslint.config.js
    import eslintJs from "@eslint/js";
    import importPlugin from "eslint-plugin-import";
    import vuePlugin from "eslint-plugin-vue";
    import globals from "globals";
    
    export default [
      {
        // 全局配置
        languageOptions: {
          globals: {
            ...globals.browser,
            ...globals.node,
            AudioWorkletGlobalScope: true,
          },
          parserOptions: {
            ecmaVersion: "latest",
            sourceType: "module",
          },
        },
        ignores: ["**/node_modules/**", "dist/**", "public/**"], // 替代 .eslintignore
      },
      eslintJs.configs.recommended, // ESLint 推荐规则
      ...vuePlugin.configs["flat/recommended"], // Vue3 推荐规则
      {
        plugins: {
          import: importPlugin,
        },
        // 自定义规则
        rules: {
          "no-console": "warn",
    
          "import/order": [
            "error",
            {
              groups: [
                "builtin",
                "external",
                "internal",
                ["sibling", "parent"],
                "index",
                "object",
                "type",
              ],
              pathGroups: [
                {
                  pattern: "vue*",
                  group: "external",
                  position: "before",
                },
                {
                  pattern: "@/hooks/**",
                  group: "internal",
                  position: "before",
                },
                {
                  pattern: "@/contexts/**",
                  group: "internal",
                  position: "before",
                },
                {
                  pattern: "@/components/**",
                  group: "internal",
                  position: "before",
                },
                {
                  pattern: "@/icons/**",
                  group: "internal",
                  position: "before",
                },
                {
                  pattern: "@/service/**",
                  group: "internal",
                  position: "before",
                },
                {
                  pattern: "@/images/**",
                  group: "internal",
                  position: "before",
                },
                {
                  pattern: "@/**",
                  group: "internal",
                  position: "before",
                },
                {
                  pattern: "./**",
                  group: "sibling",
                  position: "after",
                },
              ],
              pathGroupsExcludedImportTypes: ["builtin"],
              alphabetize: {
                order: "asc",
                caseInsensitive: true,
              },
              "newlines-between": "always",
            },
          ],
        },
      },
    ];
    
    
  4. 请注意修改vite.config.js:检查 vite.config.js 中的插件配置
    import { fileURLToPath, URL } from "node:url";
    
    import vue from "@vitejs/plugin-vue";
    import { defineConfig } from "vite";
    import eslintPlugin from "vite-plugin-eslint";
    
    // https://vitejs.dev/config/
    export default defineConfig({
      plugins: [
        vue(),
        eslintPlugin({
          // 关闭 LintOnStart 警告
          lintOnStart: false,
        }),
      ],
      resolve: {
        alias: {
          "@": fileURLToPath(new URL("./src", import.meta.url)),
        },
      },
    });
    
  5. 确认是否安装好必要的依赖以及正确的依赖版本
      "devDependencies": {
        "@babel/eslint-parser": "^7.26.8",
        "@eslint/js": "^9.20.0",
        "@vitejs/plugin-vue": "^5.0.4",
        "eslint": "^9.20.1",
        "eslint-plugin-import": "^2.31.0",
        "eslint-plugin-vue": "^9.32.0",
        "globals": "^15.15.0",
        "vite": "^5.2.8",
        "vite-plugin-eslint": "^1.8.1"
      },
    
    这里的这个globals如果不安装最新版本很坑😡😡😡,因为老版本存在一个错误的变量: 老版本导出的AudioWorkletGlobalScope在内部是这么写的:"AudioWorkletGlobalScope ",没错,它居然在最后有个空格,这就导致我们运行项目,终端会报错❌,我真是服了.....
    10:14:23 [vite] Pre-transform error: Key "languageOptions": Key "globals": Global "AudioWorkletGlobalScope " has leading or trailing whitespace
    

研发幸福感公式

代码整洁度 × 自动化程度 = 开发愉悦指数

实践收益

  • 新人上手时间减少40%(此数据并非实际量化,系博主主观判断🏳️)
  • 代码冲突率下降65%(此数据并非实际量化,系博主主观判断🏳️)
  • 心智资源释放,专注业务逻辑

灵魂拷问:当你的import顺序能像书架上的书籍分类般清晰,还会害怕需求变更吗?


结语

技术启示录:优秀的工程素养体现在对细节的极致追求。import顺序不仅是代码规范,更是研发团队的技术审美宣言。