最近和后端又干仗了。
前端这边的规范标准是变量命名必须用小驼峰结构,毕竟TS/JS约定俗称的就是这个结构。
但是后端写的接口,数据库的字段基本上全都是用的下划线。
一边是小驼峰,一边是下划线,他让我改我不改,我让他改他也拧着头不改。到底该怎么统一?
为什么前后端不能统一
很多时候我们回想能不能手动的把 user_name 改成 userName,这种做法,看似简单,实则埋了很多坑。
首先,前端 TS/JS 生态有明确约定:
变量、函数、接口、状态(state)必须用小驼峰,混用下划线会被 ESLint 报错。
也会让团队代码风格混乱,别人接手你的项目时,要反复适应两种命名,效率极低。
其次,后端和数据库用下划线,也是有原因的:
比如数据库字段不区分大小写,用下划线分隔更清晰。
后端语言(Java、Python、Go)的规范,也默认推荐下划线命名。
最关键的是:手动修改字段名,容易出现拼写错误。
比如把 create_time 写成 createTime 还是 creatTime?
一旦出错,排查起来相当麻烦。
而且如果接口字段有变动,每个用到该字段的地方都要手动修改,堪称"维护灾难"。
前端接口层自动转换
接口层自动转换是目前大厂最常用、最标准的方案。
核心实现是在前端请求/响应拦截器中,自动完成"前端小驼峰 ↔ 后端下划线"的转换,全程无感,不用手动改任何字段。
1. 准备工具库
一般都是用 lodash 库(几乎所有前端项目都会引入,轻量且稳定),它提供了现成的驼峰、下划线转换方法,不用自己写复杂逻辑。
# 安装
npm install lodash lodash-es
# 或
yarn add lodash lodash-es
2. 转换工具函数
创建一个工具文件(比如 utils/format.ts),编写两个核心函数:
一个把后端的下划线转成前端的小驼峰(请求拦截器部分),一个把前端的小驼峰转成后端的下划线(相应拦截器部分)。
import { camelCase, snakeCase } from 'lodash';
import { mapKeys, isPlainObject } from 'lodash-es';
/**
* 递归将对象的key从下划线转成小驼峰(处理后端返回的所有数据)
* @param obj 后端返回的JSON数据(对象或数组)
* @returns 转成小驼峰后的对象/数组
*/
export const toCamelCase = (obj: any): any => {
// 如果不是对象(比如字符串、数字),直接返回,不用转换
if (!isPlainObject(obj)) return obj;
// 递归遍历对象,把每个key转成小驼峰
return mapKeys(obj, (_, key) => camelCase(key));
};
/**
* 递归将对象的key从小驼峰转成下划线(给后端发请求时使用)
* @param obj 前端要发送的参数(对象)
* @returns 转成下划线后的对象
*/
export const toSnakeCase = (obj: any): any => {
if (!isPlainObject(obj)) return obj;
return mapKeys(obj, (_, key) => snakeCase(key));
};
3. 配置 Axios 拦截器
import axios from 'axios';
// 引入上面写的转换函数
import { toCamelCase, toSnakeCase } from '@/utils/format';
// 创建Axios实例(正常项目都会这么做)
const request = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL, // 你的接口基础地址
timeout: 5000
});
// 1. 请求拦截器:前端发送请求时,把小驼峰参数转成下划线
request.interceptors.request.use(config => {
// 如果有请求体(post/put请求),转换参数
if (config.data) {
config.data = toSnakeCase(config.data);
}
// 如果有URL参数(get请求),转换参数
if (config.params) {
config.params = toSnakeCase(config.params);
}
return config;
});
// 2. 响应拦截器:后端返回数据时,把下划线转成小驼峰
request.interceptors.response.use(res => {
// 转换后端返回的所有数据
if (res.data) {
res.data = toCamelCase(res.data);
}
return res;
});
export default request;
海量数据/高频接口会有性能问题吗?
其实这个问题基本上完全不用担心,毕竟这是久经验证过的方案了。
用上面的 lodash 转换函数,在现代浏览器 Node.js 中,转换速度快到可以忽略不计:
-
100 条数据:
0.0x毫秒(几乎感知不到) -
1000 条数据:
0.1~0.3毫秒 -
10000 条数据:
1~3毫秒 -
100000 条数据:
10~30毫秒
一次正常的网络请求,耗时大概是 200~1000 毫秒,转换耗时连网络请求的零头都不到。
所以,性能瓶颈永远在网络请求、DOM 渲染上,绝对不在字段名转换上。
有可能存在性能压力的场景(极其罕见)
只有同时满足以下 3 个条件,才会出现可感知的性能损耗:
-
单次接口返回大于 10000 条复杂嵌套数据(比如 5 层以上嵌套)
-
接口每秒调用几百上千次(高频场景,比如首页实时刷新)
-
没有做分页、虚拟滚动、缓存(正常项目都会做)
就算遇到这种极端场景,也有优化方案。
性能压力场景优化方案
1. 非递归转换(速度提升 5~10 倍)
绝大多数接口返回的是一层对象,不需要递归转换,只转第一层,性能会大幅提升。
前提是确定接口返回的情况。
// 非递归版本,只转第一层,性能更好(推荐优先使用)
export const toCamelCaseFlat = (obj: any): any => {
if (!obj || typeof obj !== 'object') return obj;
// 只遍历第一层key,不递归,速度极快
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [camelCase(key), value])
);
};
// 响应拦截器中替换成这个函数即可
request.interceptors.response.use(res => {
if (res.data) {
res.data = toCamelCaseFlat(res.data); // 替换成非递归版本
}
return res;
});
2. 换更快的专门库(比 lodash 快 2~5 倍)
如果觉得 lodash 不够快,可以用专门做字段转换的库:camelcase-keys / snakecase-keys,专门为高性能设计,用法和 lodash 类似。
// 安装
npm install camelcase-keys snakecase-keys
// 使用(更简洁,性能更好)
import camelcaseKeys from 'camelcase-keys';
import snakecaseKeys from 'snakecase-keys';
// 响应拦截器转换
request.interceptors.response.use(res => {
if (res.data) {
// deep: true 表示深度转换(如果有嵌套数据)
res.data = camelcaseKeys(res.data, { deep: true });
}
return res;
});
// 请求拦截器转换
request.interceptors.request.use(config => {
if (config.data) {
config.data = snakecaseKeys(config.data, { deep: true });
}
return config;
});
3. 让后端直接返回小驼峰(零前端损耗)
这是终极优化方案,后端在 ORM 层(数据库操作层)配置一下,就能直接返回小驼峰,前端完全不用做任何转换,零性能损耗。
而且在接口收发的地方可以通过转换方案将小驼峰转换成下划线,也就是将压力放在了后端。
总结
解决问题的方案很明确:
- 前端:小驼峰到底。
- 后端/数据库:下划线到底。
- 中间:自动转换到底,绝不混用、绝不妥协。