JavaScript 中,我们习惯使用 camelCase 命名规则。而实际开发中,可能会处理一些用其他命名规则命名(比如snake_case, UPPER_CASE)的东西,比如外来数据。
camelcase 和 decamelize 两个工具库,就是用于字符串命名规则转换的,它们的 npm 周下载量都在千万级。
当然也存在其他可用的工具库。这里仅介绍这两个工具库。
camelcase: Convert a dash/dot/underscore/space separated string to camelCase or PascalCase:foo-bar→fooBardecamelize:Convert a camelized string into a lowercased one with a custom separator Example:unicornRainbow→unicorn_rainbow
两个库的用法很简单,都接受一个字符串和一个选项对象作为参数。
import camelCase from 'camelcase'
import decamelize from 'decamelize'
camelCase('foo-bar')
//=> 'fooBar'
camelCase('foo_bar')
//=> 'fooBar'
camelCase('Foo-Bar')
//=> 'fooBar'
decamelize('unicornRainbow')
//=> 'unicorn_rainbow'
decamelize('unicornRainbow', {separator: '-'})
//=> 'unicorn-rainbow'
更详细的使用请参考:
对于中大型公司,后端接口通常是有规范的。而中小型公司可能更多地依赖前后端约定。我们通过一个实际的场景,对后端接口数据进行命名规则的转换,来看一下实际的应用可以是什么样子。
如果你所在项目刚好约定接口的 json 数据命名规则为 shake_case 格式,比如有一个这样的接口:
// 伪代码
const post = await getPostApi({ post_id: 22, is_published: true })
调用后返回下面的数据
{
code: 100000,
message: '',
data: { // post
id: 22,
title: 'JavaScript case convert',
content: 'This is a post...',
head_img: 'https://caseconvert.com/assets/headimg.jpg'
author: {
id: 22039,
poster_level: 1,
post_count: 12
}
create_at: 890732423,
update_at: 890732423
}
}
而前端如果要使用这些数据,代码中就出现了 shake-case 格式的代码,比如:
const headImg = post.head_img
// Vue template
<img :src="post.head_img" />
示例接口的调用的实参 { post_id: 22 }和返回数据 post 都用了 snake_case 的形式。
然而我们希望前端代码统一使用 camelCase 的命名规则,接口调用的时候这样传参数:
// 伪代码
const post = await getPostApi({ postId: 22, isPublished: true })
在真正发送 http 请求时,发出的 data 数据是 { post_id: 22, is_published: true }。并且我们希望返回的 json 数据在消费之前被转换成下面的样子:
{
code: 100000,
message: '',
data: { // post
id: 22,
title: 'JavaScript case convert',
content: 'This is a post...',
headImg: 'https://caseconvert.com/assets/headimg.jpg'
author: {
id: 22039,
posterLevel: 1,
postCount: 12
}
createAt: 890732423,
updateAt: 890732423
}
}
这样,在任何地方就能够类似这样消费返回的 json 数据了
const headImg = post.headImg
// Vue template
<img :src="post.headImg" />
那么需要做的就是下面两个点:
- http 请求发送之前,将
{ postId: 22, isPublished: true }转换成{ post_id: 22, is_published: true }。即,将camelCase的键转换成shake_case的格式。 - 在消费服务端请求数据之前,将
snake_case的键转换成camelCase的格式。
我们需要两个工具函数,toCamelCase() 用于转换成 camelCase格式,toSnakeCase() 用于转换成 snake_case格式。并且它们能转换对象或数组嵌套情况下的键。
caseConvert.js
import camelcase from 'camelcase'
import decamelize from 'decamelize'
import { isObject, isArray, isString } from './valType'
export function toCamelCase(val) {
if (isObject(val)) {
const result = {}
for (const key in val) {
const convertedKey = camelcase(key)
result[convertedKey] = toCamelCase(val[key])
}
return result
}
if (isArray(val)) {
return val.map((item) => toCamelCase(item))
}
return val
}
function toDecamelCase(val, separator) {
if (isObject(val)) {
const result = {}
for (const key in val) {
const convertedKey = decamelize(key, {
preserveConsecutiveUppercase: true,
})
result[convertedKey] = toDecamelCase(val[key], separator)
}
return result
}
if (isArray(val)) {
return val.map((item) => toDecamelCase(item, separator))
}
return val
}
export const toSnakeCase = (val) => toDecamelCase(val, '_')
isObject() 和 isArray() 实现方式很多,下面的示例仅做参考。
valType.js
const objectToString = Object.prototype.toString
export const toTypeString = (value) =>
objectToString.call(value).slice(8, -1).toLowerCase()
export const isObject = (value) => toTypeString(value) === 'object'
export const isArray = (value) => toTypeString(value) === 'array'
现在转换工具函数有了,我们需要在请求发送之前调用 toSnakeCase(data),在请求成功返回数据被消费之前调用 toCamelCase(data) 。假如我们使用 axios 用于接口调用的话,axios 拦截器就是做这些事情很好的位置。
request.js
import axios from 'axios'
const request = axios.create()
request.interceptors.request.use((config) => {
config.data = toSnakeCase(config.data)
return config
})
request.interceptors.response.use((response) => {
return toCamelCase(response.data)
})
export default request
实际开发中,还有其他场景可能需要做命名规则转换。camelcase 和 decamelize 作为基础工具库,也用在很多库中,它们的源码也很精炼,有兴趣的可以读一读,学习一下。