前端开发中命名规范的实战经验与避坑指南
项目初期的技术选型
去年接手一个后台管理系统重构,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”、“userInfo 和 userInfos 看起来像兄弟,其实是父子关系,但类型定义完全不兼容”……最后大家一致拍板:命名不是风格问题,是协作成本问题。
最大的坑:组件名和 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 个文件里拼写还不一样:getSearchConfig、genSearchConf、buildSearchConfig。
那天下午我坐在工位上,对着 Git Diff 发了 15 分钟呆,最后删掉所有 searchConfig 相关代码,重写了 Props 接口:
defineProps({
fields: {
type: Array,
default: () => []
},
apiEndpoint: {
type: String,
required: true
},
onSubmit: {
type: Function,
required: true
}
})
同时约定:所有业务层必须自己组装数据,组件只收明确字段,不收“配置对象”。名字也统一:API 地址叫 apiEndpoint(不用 apiUrl 或 endpoint),回调叫 onSubmit(不用 onSearch、handleSubmit、doSearch)。
这一改,后续加功能快多了。但代价是——我花了整整一天半,把所有调用处的 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;布尔值必须以
is、has、can开头(isLoading、hasPermission);事件回调统一onXxx(onSubmit、onCancel);API 地址统一apiEndpoint;列表数据统一items(不用list、data、rows) - 工具函数:动词开头,明确副作用。比如
formatCurrency(纯函数)、fetchUserDetail(发起请求)、validateEmail(同步校验)。严禁handleXxx和processXxx这种万金油命名。
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,但映射函数叫什么?normalizeUserResponse?adaptUserApi?到现在也没完全统一,不过影响不大,毕竟只在 service 层内部用。
还有一点想提醒:别迷信“一套规范走天下”。我们后来在另一个轻量级活动页项目里,直接用了更松的规则——因为就 3 个组件,3 个人开发,强推统一反而增加沟通成本。命名规范不是目的,减少认知负担才是。
以上是我踩坑后的总结,希望对你有帮助。如果你也有类似经历,或者有更好的约束方式(比如用 AST 自动重命名),欢迎评论区交流。

暂无评论