
题图:Pixabay
这个项目开始之初,赶时间上线。很多地方基本没怎么管代码规范,直到一期完毕回过头来看之前写的代码,不忍直视。为了之后接手的人能更加轻松,也为了自己之后debug更加顺手,边开始二期的部分需求,边重构一期的部分代码,还是有部分收获。
项目技术栈是:
- nodejs
- javascript
- react
- redux
- react-router
- webpack
之前的架构是react-router2,后面换成了react-router4。代码看起来更加的模块化。
入口模块化
项目本身是一个系统合集,用户根据权限的不同会展示不同的菜单导航。目前是有3个部分:

每个模块单独的action、router、reducer与server。

webpack切片打包
entry: { index: './index.jsx', 1: './1/router/index.jsx', 2: './2/router/index.jsx', 3: './3/router/index.jsx', vendor: [ 'react', 'react-router-dom', 'antd', 'prop-types', 'react-router-redux', 'react-redux', 'redux', 'lodash', 'react-dom', ], },
路由
根路由只需指向每个模块的路由文件即可
<Route path="/" exact component={IndexComponent} /> {/* xxx路由 start */} <Route path="/xxx1" component={1Layout} /> {/* xxx路由 start */} <Route path="/xxx2" component={2Layout} /> {/* xxx路由 start */} <Route path="/xxx3" component={3Layout} />
权限、接口转发
因为各个系统的后端不是同一批人,部署的机器也不是固定的,于是用 node 做了一层接口转发,前端页面访问固定的地址,然后通过 node 去访问不同的后端机器。同时权限部分需要携带cookie跨域,正好 node 一起做了。
测试、正式
这是之前的区分线上线下打包的地址,由于webpack没有做过多的设置,于是暂时用这样的方式区分打包环境。但是我更想用 shell 脚本来区分,可以更方便的直接调试线上接口。
const isLocal = location.host.indexOf('localhost') === 0; export const serverUrl = !IS_PRODUCTION ? devUrl : isLocal ? devUrl : `//${location.host}`;
枚举
前端静态枚举值
不允许页面出现以下字样:
- 1,2,3 ...int类型
- '成功','失败',... 等字符类型
// 静态枚举值及请求API参数 this._private = { dispatch: props.dispatch, // 路由方法 history: props.history, // 当前手机系统 os: '', ... };
错误码 ,规范化错误提示
const errnoToErrmsg = { unknownError: 1001, paramLost: 1002, paramInvalid: 1003, authFailed: 1006, signExpired: 1007, ... }
状态值 ,规范前端业务代码
const xxxStatus = { toBeAssigned: '待分配', pending: '待处理', processing: '处理中', processed: '已处理', notDealtWith: '不处理', description(rawValue) { switch (rawValue) { case this.toBeAssigned: return '1'; case this.pending: return '2'; case this.processing: return '3'; case this.processed: return '4'; case this.notDealtWith: return '5'; default: return ''; } }, }; export { FeedbackStatus };
前后端字段映射
前端是驼峰命名法,后端是下划线“_”,所以需要做一个映射,同时也更加方便debug。暂时用如下笨方法。这一块写在node那层比较好。
export const ToFE = { errno: 'errno', errmsg: 'errmsg', } export const ToBackup = { errno: 'errno', errmsg: 'errmsg', } import { ToFE, ToBackup } from '../config/define'; class Tools { /** * isObject * @param {*} data */ isObject(data) { return Object.prototype.toString.call(data) === '[object Object]'; } /** * isArray * @param {*} data */ isArray(data) { return Object.prototype.toString.call(data) === '[object Array]'; } /** * field Transfer * 后端"_"格式字段映射到前端驼峰命名字段 * @param {*} data * recursion => 递归遍历对象和数组两种类型的数据 */ fieldTransfer(data) { // 对象类型 if (this.isObject(data)) { return this.objectTransfer(data); } if (this.isArray(data)) { return this.arrTransfer(data); } return data; } // 对象遍历 objectTransfer(data) { let result = {}; for (const item in data) { if (this.isObject(data[item])) { result[ToFE[`${item}`] || item] = this.objectTransfer(data[item]); continue; } if (this.isArray(data[item])) { result[ToFE[`${item}`] || item] = this.arrTransfer(data[item]); continue; } result[ToFE[`${item}`] || item] = data[item]; } return result; } // 数组遍历 arrTransfer(data) { let result = []; for (let i = 0, j = data.length; i < j; i++) { if (this.isObject(data[i])) { result[i] = this.objectTransfer(data[i]); continue; } if (this.isArray(data[i])) { result[i] = this.arrTransfer(data[i]); continue; } result[i] = data[i]; } return result; } // 接口参数对象构建 buildParams(parameters) { if (!this.isObject(parameters)) { throw 'parameters must be array object'; } const params = {}; if (Object.keys(parameters) === 0) { return params; } for (let key in parameters) { parameters[key] !== '' && (params[ToBackup[key]] = parameters[key]); } return params; } } export default new Tools();
API层
目前 tools.buildParams(parameters) 这一块的传参还在纠结以对象传还是以数组传,以数组传直接传 arguments 就可以,缺点是传参以黑盒来传,无法判断参数,无法快速debug;优点是_fetchxxx里的参数也不需要列出,直接一个对象就行。 以对象传需要自己构建,缺点是需要重复写一次参数;优点是更加直观。
/** * xxx接口 * /xxx/v1/xxx/xxx * @param {*} * firstLevel => (可选参数) => xxx * midLevel => (可选参数) => xxx * lastLevel => (可选参数) => xxx * startAt => (可选参数) => xxx * endAt => (可选参数) => xxx */ export const _fetchxxx = ({ firstLevel = '', midLevel = '', lastLevel = '', startAt = '', endAt = '', }) => { const parameters = { firstLevel, midLevel, lastLevel, startAt, endAt }; const params = tools.buildParams(parameters); return request(`${xxx}xxx/xxx`, { method: 'post', body: qs.stringify(params), }).then(data => tools.fieldTransfer(data)); };
缓存
统一缓存命名规范,防止缓存冲突
class LocalStorage { set(key, val) { window.localStorage && localStorage.setItem(key, JSON.stringify(val)); } get(key) { return window.localStorage && JSON.parse(localStorage.getItem(key)); } remove(key) { window.localStorage && localStorage.removeItem(key); } sSet(key, val) { window.sessionStorage && sessionStorage.setItem(key, JSON.stringify(val)); } sGet(key) { return window.sessionStorage && JSON.parse(sessionStorage.getItem(key)); } sRemove(key) { window.sessionStorage && sessionStorage.removeItem(key); } } let Storage = new LocalStorage(); Storage.KEYS = { TOKEN: 'TOKEN', PV_KEY: 'PV_KEY' }; Object.freeze && Object.freeze(Storage.KEYS); export default Storage;
Feature
- 模块按需加载,自定义各模块间通信机制
- 模块注册脚手架,前端接入提供注册工具,模块单独部署,动态注册,独立 CDN。编译由服务端统一执行
- 服务端项目代码自身预编译,提高执行性能、降低部署风险
- 编译结果统一保存,支持按分支名切换、回滚
Flag
还有很多好玩的东西可以搞,每一次的优化都是对自身的一次重构。2018 fighting!!!
注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。