这里讲的架构,不是指一个项目的架构,而是指一个公司、一个团队所有整体项目的架构。
共分为上下三层:
图表如下所示:
统一放在第3层,可以由一个Http.js统一负责,包括JWT验证,token 验证都可以放在 Http.js 中自动处理。
在 Http.js 中可以引用 axios 或其它第三方类库。
关于接口缓存,也可以在Http.js中完成。示例代码:
let cache = {}; //数据缓存池
...
function bizGetCache(url, params) {
// 接口数据之临时变量
let data
if (data = cache[url]) {
return new Promise(function(resolve, reject) {
resolve(data);
});
} else {
return Http.bizGet(url, params).then(res => {
return (cache[url] = Http.getData(res));
});
}
}
在以上代码中,以接口的 url 做为 key,在用户本地内存中缓存接口返回的数据对象。仅缓存,数据有效性、完整性不作任何判断。
项目中出现的错误共有三类:
所有错误统一这样处理:
在第三层完全使用throw抛出;在第二层DAL中,所有接口统一使用catch接管;最终在第一层具体项目中统一处理所有一般性错误,方式方法例如可以发出一个弹窗。
对于前端项目,所有项目都涉及到接口调用,而这些接口调用可能在多个项目中是重复的,但这些接口的调用方式及传递的参数却是固定的。将之放在一个地方,以链式方式提供,可以显著减少开发成本、提高复用效率。
例如,api 目录下 index.js 源码示例如下:
import classroom from './classroom'
import account from './account'
import consts from './consts'
import im from './im_adapter'
import lesson from './lesson'
import cef from './cef'
import track from './track'
export default {
account,
classroom,
consts,
im,
lesson,
cef,
track
}
每一个文件,例如 classroom、account 等,均对应于后端一个 controller 类。
在使用 api 时,这样引用:
import api from '@/api'
这种声明和调用方式具有形式上扩展的自由。例如,当接口很少时,可以只定义 api.js 这样一个接口文件;当接口复杂数量增多后,可以扩展一个 api 目录,并声明一个 index.js 文件,但此时旧的引用代码不用修改。
在vue组件中调用具体 api 的代码是这样的:
let res = await api.classroom.markQuestionStatusCloseBarrage(
api.consts.classroomId,
api.consts.lessonId
);
基于 api 的链式调用,链条相当于其它语言(例如Java、C#)的命名空间。
子接口对象模块有两种输出方式,一种是输出类,另一种是输出对象。默认状态中,放在 api 访问链条中的子对象均以对象的形式输出。
对象输出的模块,相当于输出了一个全局 的singleton 类。
如果要在一个对象中,实现对另一个对象的继承,被继承者必须以类的形式输出,代码示例如下:
class Emiter{
constructor() {
this.events = {};
}
$on(event, cb) {
//多个事件
if (event instanceof Array) {
event.forEach(fn => $on(fn, cb));
}
//单个事件
if (this.events[event]) {
this.events[event].push(cb);
} else {
this.events[event] = [cb];
}
}
$emit(event) {
let args = Array.from(arguments).slice(1);
let cbs = this.events[event];
if (cbs) {
cbs.forEach(cb => cb.apply(this, args));
}
}
$once(event, cb) {
function handler() {
//先执行回调,然后清除该事件的对应回调
cb.apply(this, arguments);
$off(event, cb);
} //on函数的fn属性添加一个标记,cb,方便循环off清除(提供了事件与回调的时候)
handler.cb = cb;
$on(event, handler);
}
$off(event, cb) {
if (!arguments) {
this.events = {};
}
if (event instanceof Array) {
event.forEach(e => $off(e, cb));
}
if (!cb) {
this.events[event] = null;
}
if (cb) {
let cbs = this.events[event];
if (cbs) {
for (let i = 0; i < cbs.length; i++) {
if (cb === cbs[i] || cb === cbs[i].cb) {
cbs.splice(i, 1);
break;
}
}
}
}
}
}
export default Emiter;
这是一个Emiter类,实现了与VUE事件模式相同接口的事件观察者模式。假如有一个接口对象需要派发事件,可以在对象中这样继承Emiter:
export default {
__proto__: new Emiter()
...
}
而如果子接口对象要输出类,而不是对象(虽然一般不需要这样做,这里只展示实现方式)可以这样实现:
class SomeInterfaceObject extends Emiter{...}
鉴于windows与linux大小写敏感不一致,而命名规范主要关乎大小写,所以这个问题值得重视。命名是小问题,处理不好,代码不易读事小,引起难以跟踪的Bug事大。
不知道我讲明白没有,以上。
石桥码农 2019/04/04