规范能够大量减少沟通成本,另外笔者认为它还有一个隐性的作用,如果有了规范,在写代码的时候可以省略很多思考和修改,从而增加开发效率。所以笔者一直对规范这块有着一种执念,总会在规范方面想很多。比如最近就在思考API的一些规范,参考RESTful API的思想,微创新出了一套过渡性的类REST规范,可以在仅用GET、POST的情况下,形成一定的规范。
下面用一个 公司列表 -> 公司详情 -> 部门列表 的场景来举例对比
RESTful | 类RESTful | 随心所欲版 | |
---|---|---|---|
公司-查 | GET /corps | GET /corps | GET /corps |
公司-增 | POST /corps | POST /corps | POST /corp/create |
公司-删 | DELETE /corps/:corpID | POST /corps/:corpID/delete | POST /corp/delete |
公司-改 | PUT /corps/:corpID | POST /corps/:corpID/put | POST /corp/modify |
公司-局部改 | PATCH /corps/:corpID | POST /corps/:corpID/patch | POST /corp/update |
公司详情-查 | GET /corps/:corpID | GET /corps/:corpID | GET /corp |
部门-查 | GET /corps/:corpID/departs | GET /corps/:corpID/departs | GET /depart/list |
部门-增 | POST /corps/:corpID/departs | POST /corps/:corpID/departs | POST /depart/add |
部门-删 | DELETE /corps/:corpID/departs/:departID | POST /corps/:corpID/departs/:departID/delete | POST /depart/remove |
部门-改 | PUT /corps/:corpID/departs/:departID | POST /corps/:corpID/departs/:departID/put | POST /depart/save |
部门-局部改 | PATCH /corps/:corpID/departs/:departID | POST /corps/:corpID/departs/:departID/patch | POST /depart/change |
类RESTful规范说明:
- 只可以在最后出现动词
- 名词要用复数
- “新建”时不需要动词后缀,POST+名词复数即可
- 局部修改,如修改部门的leader、英文名等,统一用patch,修改的字段在传的data中声明
如果自己的项目还有什么特殊的需求,可适当修改为自己的规范。总之,要有一个清晰的!清晰的!清晰的!规范,来降低沟通成本,提高效率。
另外,由于类RESTful允许在最后增加动词,这就较RESTful更为灵活,对于一些特殊的场景也可以应付自如。比如导入、导出、从其他数据源同步等CURD之外的场景,就可以用不同的动词,如import、export、sync等来进行区分。
再说说数据结构。如果有一个规范的数据结构,那么无论对于前端还是后端来说,都有利于集中管理,封装之后可以节省很多代码。
{
"code": 200,
"msg": "success",
"data": {
"corps": [
{
"id": 123,
"name": "A Corp"
}
],
"corp_count": 99,
"departs": [
{
"id": 456,
"name": "A depart"
}
],
"depart_count": 49
}
}
- code、msg、data必须有,且不多不少。
- code可简单的定义为0、1,表示成功与否,也可参考HTTP响应码
- msg为给用户展示的信息,可与code关联,统一维护一个字典
- data必须为object,返回数据都要被其包裹。如果没有数据返回时,至少要让data返回一个空对象,即“{}”
最后说说前端请求的封装。封装后要达到以下效果:
-
返回结果统一处理,不用到处写if(code===200){}else if(code===300){}之类的代码
-
封装与业务隔离,更换其他库的成本低,axios、jquery等说换就换,不用修改太多代码
-
支持特殊需求配置,比如成功后是否弹窗、弹窗文字变化等
-
支持缓存,防止一些公用接口多次请求浪费资源
封装层示意:
// ajax.js 封装层示意
function ajaxBase(config) {
const { url, data, type = 'get', dataType = 'json', cache = false, succuss, error } = config;
$.ajax({
url,
data,
type,
dataType,
cache
}).done((res) => {
if (res.code === 200) { // 统一成功处理
if (succuss) {
succuss(res.data);
} else {
console.log('Success!');
}
} else if (res.code === 302) { // 统一跳转
console.log('Redirect to other place!');
location.href = data.url;
} else { // 统一错误处理
if (error) {
error(res);
} else {
console.warn(JSON.stringify(res));
}
}
});
}
export function getAjax(url, data, sucFun, errFun) {
return ajaxBase({ url, data, succuss: sucFun, error: errFun });
}
export function postAjax(url, data, sucFun, errFun) {
return ajaxBase({ url, data, type: 'post', succuss: sucFun, error: errFun });
}
封装层是统一管理请求的地方,此处尽量封装公共逻辑,比如上例中的默认成功回调、失败回调、以及不同返回码的回调等。
另外一个重点是暴露。不管上面的封装用的是jquery、axios或是其他,此处暴露出去的api传参一定要保持不变。这样在更换ajax核心库的时候,后面的业务层不需要做改动。目前,效果还没有体现出来,下面来看下业务调用层的情况,将会省下很多代码。
业务层示意:
// 业务层封装
import { getAjax, postAjax } from './ajax.js';
export function getUsers(data, sucFun) {
return getAjax('/users', data, sucFun);
}
export function updateUser(data, sucFun) {
return postAjax(`/users/${data.userId}`, data, sucFun);
}
// 业务层调用
getUsers({ page: 1, pageSize: 10 }, ({ data }) => {
// do success
});
// 走默认sucFun
updateUser({ userId: 123, name: 'userName' });
通常在业务层的模块里,笔者也会做一次ajax的封装,便于统一管理。在上例可以看到,业务层的封装基本上就是把url传了进去。另外,是否走cache也应该在这里进行控制,为了不让例子太复杂,笔者在这里没有体现出来,留给读者自己实现。在最终的调用层可以看到,请求变得清晰简单许多。
对于promise风格的封装,可能就有些不同了,不过思路都是一样的,只要能达到上面总结的各种效果,就是好的封装。