前端开发中命名规范的实战经验与避坑指南

♫艺茹 前端 阅读 1,136
赞 35 收藏
二维码
手机扫码查看
反馈

项目初期的技术选型

去年接手一个后台管理系统重构,Vue 3 + Vite + TypeScript,目标是把原来跑得像老拖拉机的 Vue 2 单页应用换成能撑住 50+ 路由、20+ 表单模块的现代架构。命名规范这事,一开始真没当回事——反正 ESLint 有 @typescript-eslint/naming-convention,配个规则就完事了?

前端开发中命名规范的实战经验与避坑指南

结果上线前两周,光是 Code Review 就卡在命名上:有人写 getOrderListData,有人写 fetchOrders,还有人直接 getData……同一个 API 封装,在三个文件里出现了四种名字。最离谱的是有个同事把 Vuex 的 action 命名叫 handleClickSubmitBtn,我盯着看了三秒,没敢点进去——怕进去之后要重写整个状态流。

于是我们临时开了个会,不是聊技术方案,是专门聊“怎么叫名字”。会上没人提 BEM 或 CSS-in-JS,全在吐槽:“上次改个按钮文案,结果连带改了 7 个文件,因为变量名里硬编码了 text”、“userInfouserInfos 看起来像兄弟,其实是父子关系,但类型定义完全不兼容”……最后大家一致拍板:命名不是风格问题,是协作成本问题。

最大的坑:组件名和 Props 混用导致的重构灾难

真正踩进坑是在封装一个通用搜索表单组件时。我们按惯例叫它 SearchForm.vue,Props 定义如下:

defineProps({
  searchConfig: {
    type: Object,
    required: true
  },
  onSearch: {
    type: Function,
    required: true
  }
})

看起来很干净对吧?但问题出在 searchConfig 这个字段上。不同业务方传进来的东西五花八门:{ fields: [...], api: '/user/search' }{ fields: [...], api: 'https://jztheme.com/api/search' }、甚至还有人传 { fields: [...], api: () => fetch('https://jztheme.com/api/search') }

开始我没当回事,想着“配置对象嘛,灵活点好”。结果两周后,产品提了个需求:所有搜索加个“高级筛选”折叠面板。我改完组件逻辑,准备发 PR,结果发现——6 个页面里,有 4 个传的 searchConfig 是直接从父组件 data 里取的,根本没做响应式处理;另外 2 个用了 computed,但 computed 里又调了另一个 getSearchConfig 函数,而这个函数名字在 3 个文件里拼写还不一样:getSearchConfiggenSearchConfbuildSearchConfig

那天下午我坐在工位上,对着 Git Diff 发了 15 分钟呆,最后删掉所有 searchConfig 相关代码,重写了 Props 接口:

defineProps({
  fields: {
    type: Array,
    default: () => []
  },
  apiEndpoint: {
    type: String,
    required: true
  },
  onSubmit: {
    type: Function,
    required: true
  }
})

同时约定:所有业务层必须自己组装数据,组件只收明确字段,不收“配置对象”。名字也统一:API 地址叫 apiEndpoint(不用 apiUrlendpoint),回调叫 onSubmit(不用 onSearchhandleSubmitdoSearch)。

这一改,后续加功能快多了。但代价是——我花了整整一天半,把所有调用处的 Props 重命名、补类型、修 TS 报错。其中有一个地方漏改了,上线后搜索按钮点了没反应,日志里打出来是 TypeError: props.onSearch is not a function,而实际代码里传的是 onSubmit。这种低级错误,就是因为命名不收敛,靠人肉记忆导致的。

最终的解决方案

我们没搞什么高大上的命名文档,就在项目根目录建了个 docs/naming.md,内容就三块:

  • 组件名:PascalCase,语义化,禁止缩写。比如 UserProfileCard.vue ✅,UPC.vue ❌,UserCard.vue ❌(太泛,跟 OrderCard.vue 冲突)
  • Props:kebab-case 不允许(Vue 模板里不好写),全部 camelCase;布尔值必须以 ishascan 开头(isLoadinghasPermission);事件回调统一 onXxxonSubmitonCancel);API 地址统一 apiEndpoint;列表数据统一 items(不用 listdatarows
  • 工具函数:动词开头,明确副作用。比如 formatCurrency(纯函数)、fetchUserDetail(发起请求)、validateEmail(同步校验)。严禁 handleXxxprocessXxx 这种万金油命名。

ESLint 配置也同步更新了:

{
  "@typescript-eslint/naming-convention": [
    "error",
    {
      "selector": "variableLike",
      "format": ["camelCase"],
      "leadingUnderscore": "allow"
    },
    {
      "selector": "function",
      "format": ["camelCase"],
      "prefix": ["use", "fetch", "format", "validate", "get", "set"]
    }
  ]
}

重点加了 prefix 规则——函数名必须带前缀,不然直接报错。这招治好了我们团队 80% 的“随便起名”毛病。

回顾与反思

现在回头看,这套规范不是最优雅的,但它解决了最痛的点:协作时不用猜、不用问、不会传错。上线两个月,命名相关的 CR comment 从平均每次 PR 3 条降到了 0.2 条(基本是新人忘了改)。

当然还有没解决的细节:比如有些旧接口返回的字段名是 user_name,我们本地映射成 userName,但映射函数叫什么?normalizeUserResponseadaptUserApi?到现在也没完全统一,不过影响不大,毕竟只在 service 层内部用。

还有一点想提醒:别迷信“一套规范走天下”。我们后来在另一个轻量级活动页项目里,直接用了更松的规则——因为就 3 个组件,3 个人开发,强推统一反而增加沟通成本。命名规范不是目的,减少认知负担才是。

以上是我踩坑后的总结,希望对你有帮助。如果你也有类似经历,或者有更好的约束方式(比如用 AST 自动重命名),欢迎评论区交流。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论