- 作者:
项目背景:
G买卖H5是运行在多端的游戏交易平台。基于产品层面的功能升级以及提高开发效率的需求,前段时间我用Vue和Webpack对项目进行了一次渐进式的重构。所谓渐进式,即每个周期仅对部分页面进行改造,不影响其他业务的开展。这次我改造的是我买到的/我卖出的订单列表以及订单详情。
此次分享主要有以下几个点:
- 核心技术
- 实现组件化
- 数据管理
- 代码层面改善点
- 遇到的坑
- 持续优化点
核心技术
这次重构用到的核心技术是vue2.0和webpack1.0。接下来对它们进行简要的介绍。
Vue.js是一套构建用户界面的渐进式框架。 与其他重量级框架不同的是,Vue 采用自底向上增量开发的设计。 Vue 的核心库只关注视图层,并且非常容易学习。具体可以从学习它的用法。
Webpack是当下最热门的前端资源模块化管理和打包工具,它可以将很多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源,还可以将按需加载的模块进行代码分割,等到实际需要的时候再异步加载。
下图是官方对Webpack的简介,通过这幅图看出,相互依赖的模块文件,会被打包成一个或多个js文件,可以减少HTTP的请求次数。
Webpack的具体用法可以参照 。此次重构用到的Vue版本是2.1.0,Webpack是1.13.2。
目录结构:
gmmh5 |-- build //构建目录 | |-- build.js | |-- dev-client.js | |-- dev-server.js | |-- HashedModuleIdsPlugin.js | |-- utils.js | |-- webpack.base.conf.js | |-- webpack.dev.conf.js | |-- webpack.prod.conf.js |-- config //配置文件 |-- dist //打包后的文件 |-- src //源码目录 | |--assets // 静态资源 | |--biz // 页面配置文件 | |--common // 公共方法 | |--components // 基础组件 | |--constants | |--modules // 业务组件 | |--pages // 页面 | |--utils // 工具函数复制代码
如何实现组件化?
组件化的好处不一一列举了,职责单一,易维护,可扩展...
首先我将这两张页面分别按功能和展现划分了组件。
如下图的订单列表页面,页面从上到下按照功能可以划分为三个组件:选择商品类型,切换交易状态,以及渲染列表数据的组件
选择商品类型select-type组件负责改变goodsType, 切换交易状态change-state组件负责改变state, 两个组件都将改变的参数传到入口文件app.vue,由入口文件负责获取列表数据,再将数据传到list组件,负责展现列表数据。数据流动如下图: 订单详情也是同样的处理方法。关于组件间的通讯,父组件的数据通过prop下发到子组件中,子组件通过vue的$on, $emit 事件接口来与父组件通信。兄弟组件或者层级比较深的组件,在目前没有采用vuex的情况下,使用EventBus进行通讯。具体操作方法如下:
首先命名一个event-bus.js,创建一个新的全局Vue实例,命名为EventBus并且导出该对象。
import Vue from 'vue'var EventBus = new Vue()export default EventBus复制代码
在组件A中同时引入Vue和EventBus。当这个组件中的方法“emitMethod”被调用时,它触发了事件”EVENT_NAME”,并且传递了“payload”参数。
import Vue from 'vue';import EventBus from 'event-bus'Vue.component('component-a', { ... methods: { emitMethod () { EventBus.$emit('EVENT_NAME', payLoad); } }});复制代码
在另外一个组件B中,我们可以注册一个监听事件,来监听由EventBus传递来的事件“EVENT_NAME”。
import Vue from 'vue';import EventBus from './event-bus';Vue.component(‘component-b’, { ... mounted () { EventBus.$on('EVENT_NAME', function (payLoad) { ... }); }});复制代码
这样B组件就可以监听A组件触发的事件,而不需要考虑过多的层级问题。
数据管理
处理订单详情复杂的部分在于它的页面展示取决于商品类型与交易状态值。
G买卖H5页面有以下几种商品类型,其中账号还分为手游,端游,外部寄售账号。点券也包含撮合点券,普通点券的类型。实际处理的商品类型比下图复杂。
每个商品类型下有不同的状态值,其中账号的状态值最多。商品类型和状态值共同决定了页面中内容的显示:状态说明的标题,顶部文字说明,交易图标以及操作项按钮。 为了处理这类数据 ,不与页面逻辑杂糅在一起,我将其做成了配置表。部分代码如下:const account = { '1': { 'title': '待付款', 'topImg': imgState.wait, 'topMsg': topMsgSource.fromFixTxtByFunc, 'btns': [btn.cancel, btn.pay] }, '2': { 'title': '付款成功', 'topImg': imgState.trade, 'topMsg': topMsgSource.fromFixTxtByFunc, 'btns': [btn.refund, btn.selectCheckType] }, ...}复制代码
account代表商品类型中的账号类型。'1', '2'则是状态值。
取值时在页面中调用商品详情的接口,获取到数据detail,取出detail中的goods_type和state_to_out,即商品类型和商品交易状态值。然后在配置config文件文件中取得对应的数据。譬如,取状态说明的标题title:
const vm = thislet goods_type = vm.detail.goods_typelet state_to_out = vm.detail.state_to_outtitle = vm.config[goods_type][state_to_out]["title"]复制代码
这样的好处是一目了然,要进行任何的改动在配置表里改动即可。
代码层面改善点
减少重复的代码
原先的代码,存在的情况是:一份代码在多个地方复制,改动时经常不知道哪里改了哪里漏了。我将许多重复的代码梳理一遍,抽象封装重复的代码逻辑。
譬如操作按钮中的很多函数,调用了ajax之后都会刷新页面,于是我将其抽出来单独写成一个函数:
operateRefresh(url, params) { //通用方法 -- ajax后刷新页面 const vm = this utils.get(url, params, (res) => { let data = res.data if (res.return_code === 0) { vm.refresh() } else { utils.toast(res.return_message) } })}复制代码
单一职责原则编写方法
单一职责就是一个对象(方法)只做一件事。 如果一个方法承担了过多职责,那么一个职责发生变化可能会影响其他职责的实现,修改代码就变得比较危险。
此次改造我将一些大的函数按功能细分成一些小的功能函数。譬如列表页在初始时要做的事情包括:判断买单还是卖单,获取初始化的商品类型,调取接口获取列表数据以及监听其他组件的事件:
created() { const vm = this // 判断用户是买家还是卖家 vm.judgeRole() // 从url获取goods_type以及trade_mode vm.getUrlParams() // 获取列表数据 vm.getList() // 监听事件 vm.subscribeToEvent() }复制代码
遇到的坑
重构过程中遇到的坑更多是由于对业务的不够充分理解上,这回用Vue2.0来重构也遇到了一些Vue的坑。
由于 Vue 会在初始化实例时对属性执行 getter/setter 转化过程,所以属性必须在 data 对象上存在才能让 Vue 转换它,才能检测到它的变化。Vue是不能检测到属性的添加或删除的。在部分业务中我对数据属性进行添加,以及修改,却没有引发Vue的重新渲染,在这个点上卡住了一些时间。后来阅读了官方文档,发现它可以使用 Vue.set(object, key, value) 方法将响应属性添加到嵌套的对象上,从而触发组件的更新:
Vue.set(vm.someObject, 'b', 2)复制代码
另外,由于Vue 异步执行 DOM 更新。只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会一次推入到队列中。如果想要在数据变化之后立即更新DOM,可以在数据变化之后立即使用 Vue.nextTick(callback) 。
Vue.nextTick(function () { // 数据变化})复制代码
持续优化点
业务完成了,我还整理了一些可以持续优化的点:
- 性能。部分文件体积可以近一步缩小,可以更有效地利用缓存,提升首屏渲染速度。
- 体验。提升下拉刷新的体验以及添加页面交互时的提示。
- 数据流。整合更多业务进来时,对数据流的结构设计要更加清晰。
- 组件化。基础组件可以更抽象化,利于复用。
总结
经过这次的重构,我对业务有了更深入的了解和掌握,并且认识到在面对复杂的业务时,停下来思考清楚业务场景比动手写代码重要得多。
此次对Vue也有了更深入的了解和掌握,也提醒了自己在日常开发中需要不断提高编码能力和代码的质量。未来,更多业务整合到重构的新项目中,在其他方面也需要不断地优化和学习。