经过对项目结构 / 开发方式 / 技术架构的研究,再次提出部分可以优化的点。
刚加入新公司之后,在学习来新公司的开发技术之后,就迫不及待的想要将自己过往的开发方式和技巧融合到现在的项目中,下面是针对目前项目的中一些技术点的改进思路
此方案主要由如下几点组成:
- 代码编写规范
- Api层抽离
- Request工具更高效封装
- 路由分离配置方案
- 目录结构整理
- 数据请求优化
- Mock数据配置
方案在不断的编写过程中,其他内容还需大家共同努力发现,整理。
代码编写规范
良好的代码编写规范可以大幅度提高项目开发效率,增加代码的可读性/可维护性/可测试性,减少开发/重构等场景的复杂耗时程度。
代码规范可以划分为构建规范/编写规范等方面,例如项目结构的搭建,说明文档的维护/模块的构建与命名/代码中注释与业务代码编写方式等方面都应该考虑到并设立统一的/较优的规则来进行约束,下面就进行简单说明:
项目结构重构
项目结构构建过程中,应该不断维护《目录结构说明文档》,并且建立项目结构版本,如:1.0.0,并且应该标注如下几点:
- 结构中新建与改动过程的动作说明
- 项目结构版本信息
目录与模块文件命名
根据目前已经处于开发过程中的项目,整理部分规则如下:
-
一般目录命名采用lowercase方式,,目录由多个单词组件则利用"-"连接,如:
router,
store,
components,
store/modules,
utils/dygraph-plugin
-
views中路由视图目录采用大驼峰形式命名,如:
views/Algorithms
views/Events
views/Monitor
-
工具模块, 基础业务模块文件采用lowercase方式,目录由多个单词组件则利用"-"连接,如:
bin/babel-external-helpers.js (babel中的命名方式)
store/modules/event.js
-
组件命名采用大驼峰方式命名,如:
AlgorithmsCenter.vue
AlgorithmsDdetail.vue
具体命名方式可以参考Vue.js风格指南 。
代码编写规范
代码编写过程中应遵守基本的开发规范准则,在这里只是给出部分建议。
BEM(Block-Element-Modifier)结构命名方式
HTML编写DOM结构并标时尽量遵守BEM命名方式,具体请参考文献:BEM--前端命名规范介绍,BEM —— 源自Yandex的CSS 命名方法论。
注意:没必要真的在每个地方都用上它,当某个节点不属于任何一个BEM范畴的时候,按照常规命名方式就可以。
OOCSS(Object Oriented CSS)编写方式
OOCSS(面向对象CSS编写)并不是什么新奇的技术,它想表明的其实就是我们可以将样式与固定的dom解耦,将一个或多个细小的css样式添加到需要它们的dom节点身上,目的其实还是为了提高代码的复用率,并且代码可读性也会有很大的提高,在项目中特别推荐使用BEM + OOCSS的开发方式,网上有很多结合使用指南,这里给大家提供一个非常简洁的CSS的组件化方案:OOCSS + BEM。
Javascript规范可以参考Eslint中更多详细的规则,Google Javascript规范也是不错的选择,比如:
1. 拒绝var
2. 使用空格代替tab
3. 优先使用箭头函数
4. 使用模版字符串代替连接字符串
5. ...
这里提供给大家来自阿里的Kissy 最佳编码实践。
注释方式
对于注释其实应该更加看重,好的注释可以大幅度提高代码的可读性。
HTML文档中应该对页面每一个Block的开始和结束进行注释说明,单独的Element也要标明,例如:
CSS中也应该根据所编写的代码划分结构片段后注释,尽可能使用多行注释方式(/**/),如下:
/* css reset start*/
*{
box-sizing: border-box
}
body {
margin: 0;
background-color: #FFF;
font-family: MicrosoftYaHei, PingFangSC, Helvetica, Arial, sans-serif, "宋体";
}
// ...
/* elemnt-ui table 样式重置 */
.el-table tr {
background-color: #f3f3f3;
cursor: pointer;
}
// ...
JS中的注释希望能采用JSDoc推荐的方式,这样更有利于开发到某一阶段时,利用JSDoc工具直接生成关于该JS模块的文档,具体可以参考文档:JSDoc在线文档,JSDoc中文文档
/**
* 为了演示JSDoc的示例模块.
* @module utils/jsdoc
* @see module:main.js
*/
/** 暴露 name */
export const name = 'mixer';
/**
* 错误处理方法.
* @param {object} self - 调用方法的vue实例.
* @param {object} error - 错误对象.
* @param {string} errorMessage - 出错提醒信息.
* @return {string} 处理后的错误信息文本.
* @example
* errorTip(this, e, '登陆失败')
*/
export function errorTip (self, error, errorMessage) {
// 处理后的错误文本信息
let message = translate(error.message || error.msg || '出错了,请重试')
console.log(error, message)
// 调用vue实例的消息提示方法
self.$message({
type: 'error',
message: errorMessage || message,
center: true,
duration: (error.message && error.message.indexOf('Network Error') !== -1) ? 8000 : 3000
})
return message
}
/**
* 错误文本信息处理方法.
* @param {string} message - 原始的error对象中的报错信息.
* @return {string} 处理后的错误信息.
*/
function translate (message) {
if (message.indexOf('timeout') !== -1) {
return '请求超时'
} else if (message.indexOf('Network Error') !== -1) {
return '网络错误'
} else {
return '出错了,请重试'
}
}
生成文档的方式:
-
下载jsdoc-to-markdown 可以将文档输出为markdown
npm install --save-dev jsdoc-to-markdown
-
配置scripts
{ "scripts": { "docs": "jsdoc2md lib/*.js > api.md" } }
-
执行任务
npm run docs
即可将lib下所有的js文件根据jsdoc规范生成到api.md中。
Request工具高效封装
针对axios进行了更高效的封装,比如设置了拦截器,在数据请求回来后就可以根据请求结果的状态进行失败处理等等。
config/axios.config.js:
/**
* axios 配置模块
* @module config/axios.config
* @see utils/request
*/
import qs from 'qs';
/**
* axios具体配置对象
* @description 包含了基础路径/请求前后对数据对处理,自定义请求头的设置等
*/
const axiosConfig = {
baseURL: process.env.RESTAPI_PREFIX,
// 请求前的数据处理
transformRequest: [function (data) {
return data;
}],
// 请求后的数据处理
transformResponse: [function (data) {
return data;
}],
// 自定义的请求头
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/x-www-form-urlencoded'
},
// 查询对象序列化函数
paramsSerializer: function (params) {
return qs.stringify(params);
},
// 超时设置s
timeout: 10000,
// 跨域是否带Token 项目中加上会出错
// withCredentials: true,
// 自定义请求处理
// adapter: function(resolve, reject, config) {},
// 响应的数据格式 json / blob /document /arraybuffer / text / stream
responseType: 'json',
// xsrf 设置
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
// 下传和下载进度回调
onUploadProgress: function (progressEvent) {
Math.round(progressEvent.loaded * 100 / progressEvent.total);
},
onDownloadProgress: function (progressEvent) {
Math.round(progressEvent.loaded * 100 / progressEvent.total);
},
// 最多转发数,用于node.js
maxRedirects: 5,
// 最大响应数据大小
maxContentLength: 2000,
// 自定义错误状态码范围
validateStatus: function (status) {
return status >= 200 && status < 300;
},
// 用于node.js
// httpAgent: new http.Agent({ keepAlive: true }),
// httpsAgent: new https.Agent({ keepAlive: true })
};
/** 导出配置模块 */
export default axiosConfig;
utils/request.js(目前使用的是restapi.js):
/**
* 业务中使用的ajax请求工具模块
* @module utils/request
* @see main.js
*/
import axios from 'axios';
import config from '../config/axios.config';
import qs from 'querystring'
import Vue from 'vue'
import { errorTip } from 'utils/common'
// 用来调用errorTip的vue实例
const vueInstance = new Vue()
// 构建得的请求对象
const request = axios.create(config);
// 返回状态判断(添加响应拦截器)
request.interceptors.response.use(
res => {
// 如果数据请求失败
if ( res.data.code > 300 ) {
errorTip(vueInstance, res.data)
return Promise.reject(res.data);
}
return res.data.data;
},
error => {
return Promise.reject(error);
}
);
request.interceptors.request.use((config) => {
let allowMethods = ['post'];
if (allowMethods.indexOf(config.method) !== -1) {
config.headers['Content-Type'] = 'application/x-www-form-urlencoded';
config.data = qs.stringify(config.data);
}
return config;
}, (error) => {
return Promise.reject(error);
});
// 对axios的实例重新封装成一个plugin ,方便 Vue.use(xxxx)
export default request;
Api抽离
在src开发目录中建立api目录结构,在其中可以集中构建所有请求动作的模块,以便调用。
api/index.js:
/**
* api接口调用维护模块
* @module api
* @see main
*/
import request from 'utils/request'
import Vue from 'vue'
import { errorTip } from 'utils/common'
// 用来调用errorTip的vue实例
const vueInstance = new Vue()
/**
* 分页获取算法列表
* @param {Object} [p] pageInfo 页码相关信息
* @param {number} [p.page] page 页数
* @param {number} [p.size] size 每页数据个数
* @param {string} [p.labels] labels 标签
* @param {string} [p.query] query 查询条件
* @return {Promise} request 请求动作promise
*/
export let apiGetList = ({page = 1, size = 10, labels = 'anomaly', query = ''} = {}) => {
return request.get(`/algorithm?page=${page}&labels=${labels}&size=${size}&query=${query}`).catch(error => errorTip(vueInstance, error))
}
数据请求动作优化
利用async await对请求作出优化处理,在需要调用请求的地方利用async/await来规避回调函数的嵌套。
store/modules/setting.js:
import { apiGetList } from 'api'
// ...
actions: {
// 分页获取算法列表 当前写法
getList: ({ commit }, {page = 1, size = 10, labels = 'anomaly', query = ''} = {}) => {
return new Promise((resolve, reject) => {
restapi.request({
method: 'get',
url: `/algorithm?page=${page}&labels=${labels}&size=${size}&query=${query}`,
success: sdata => {
commit('LIST_ALGORITHMS', sdata)
resolve(sdata)
},
error: reject
})
})
},
// 分页获取算法列表 重构后写法
getListTest: async ({ commit }, params) => {
let sdata = await apiGetList(params)
commit('LIST_ALGORITHMS', sdata)
}
// ...
}
视图中调用方式基本区别不大,不需要再做请求错误处理。
views/Algorithms/AlgorithmsCenter.vue
methods: {
...mapActions('algorithm', [
'getList',
'getListTest',
'getCount'
]),
// ...
getAlgorithmsList ({start = 1, length = 10} = {}) {
// this.getList({page: Math.ceil(start / length), size: length, labels: this.algoType}).catch(error => errorTip(this, error)) // 当前调用方式
this.getListTest({page: Math.ceil(start / length), size: length, labels: this.algoType})// 重构后调用方式
}
// ...
}
Mock数据搭建
mock数据的搭建有利于在前后端开发进度不同步的情况下进行模拟数据请求,就可以根据请求完成前端的交互逻辑,合理的配置可以在有真实接口后稍作更改就可以调用线上真实接口,处理手法更平滑。
Mock的搭建有很多种方式,在这里推荐使用json-server的使用方式,本质就是利用json-server快捷的启动一个mock服务器,然后在config/index/dev/proxyTable中代理请求到mock服务器即可。
具体配置过程可以参考 json-server配置详解
其实EasyMock这个网站也提供来便利的mock数据的方式,不再需要自己去搭建mock环境,简直是非常棒了