在上篇文章中,我讲到我们的组件开发模式解决了以下几个问题
- 渲染逻辑,业务逻辑,事件分发,控制器彻底分离
- 组件内部只有一个状态
- 渲染逻辑只有一处
- 只绑定一次事件
- 没有任何学习成本
下面来看我们具体我们是通过什么方式解决的
渲染逻辑,业务逻辑,事件分发,控制器彻底分离,易于维护和重用
我们在原来的开发中一般都是把这些代码都写在同一个文件中,整个文件上千行是常有的事。在维护代码或者根据需求的变化修改代码的时候会变得极其困难,从团队的角度来说,根本没法互相协作。为了解决这些问题我们把一个组件拆分为渲染逻辑,业务逻辑,时间分发,和控制器。下面是我们开发一个组件时的文件结构
- index.js 组件的控制器: 主要负责组件的变量的初始化,这些变量主要包括组件的container,template,event,store,initData;根据不同的状态拿到对应的模版;virtual dom的转换,diff,pattch;事件的绑定与解绑。 - store.js 业务逻辑: 主要负责在内存中对业务逻辑的处理,如add,remove,modify,query等的操作,处理完成后把最终的状态告诉组件的控制器,组件只会存在这样的一个唯一的状态;同时也负责从service中取得数据。 - tempate.js 渲染逻辑: 该文件中都是组件的不同的状态对应的模版方法,每个方法都会根据传入的组件状态参数,和相对应模版结构组合为最终的html. - actions.js 事件分发: 该部分主要用于组件的对外通信,如果组件内部的变化需要通知其他组件,就需要该部分来协作完成。
组件内部只有一个状态
组件内部只有一个状态是通过Store来维护的,这里所说的状态其实就是组件需要的数据。组件的初始化数据会传给store,在每次事件的操作会触发store中的对数据的处理方法,等数据更新完成会发出一个updateData的事件告诉组件控制器。具体的实现看下面的代码
createStore: function(){ var Store = this.get('store'); var that = this; if(Store){ var initData = this.get('initData'); var storeObj = new Store({ data:initData }); storeObj.on('updateData',function(e){ var data = e.data.data; that.updateView(data); }); this.store = storeObj; } }
渲染逻辑只有一处
组件的所有渲染都是通过一个render方法进行.在render方法中拿到最新的html字符串,通过VirtualHtml转换为VirtualDom,如果是首次渲染就直接附加到真正的dom树上;如果是以后渲染,需要拿最新的VirtualDom和上次的VirtualDom进行diff,把diff所产生的patches,patche到真正的dom树上。具体的实现看下面的代码
render: function(data,cb){ var that = this; var firstRender = !this.tree; this.renderedTemplate = this.buildHTML(); //把字符串转换为dom VirtualHtml(this.renderedTemplate, function (err, dom) { if (err) { if (cb) return cb(err); throw err; } if (firstRender) { that.tree = dom; that.el = CreateElement(dom); } else { var patches = Diff(that.tree, dom); that.tree = dom; that.el = Patch(that.el, patches); } if (cb) cb(null, that); }); }
只绑定一次事件
会在组件的容器上或者页面的body上绑定一次事件,以后组件内部的所有的事件操作都是基于这个事件代理外分发。具体的实现代码如下
delegateEvents: function(events) { //默认通过参数传入,也可以通过配置属性传入 events = events || this.get('events'); if (!events) return this; //循环所有事件,并代理 for (var key in events) { var method = events[key]; if (!S.isFunction(method)) method = this[events[key]]; if (!method) continue; var match = key.match(delegateEventSplitter); this.undelegate(match[1], match[2]); this.delegate(match[1], match[2], S.bind(method, this)); } return this; }
没有任何学习成本
* 基于开发这都很熟悉的字符串模版 * virtual dom的相关操作封装在所有组件的基类中,对开发者不透明
这里是源码文件http://g.tbcdn.cn/de/cicada/1.0.0/widget/base-widget/index.js,这篇文章其实是一篇对源码的解读。
最后我想说的是,我们数娱用到的ui组件开发模式,都不是最新的或者我们发明的[实际上大部分的框架也都是这样,没有发明什么新技术],我们做的只是把开发过程中的重复行为或者容易犯错误的地方封装在框架中,从根本上解决了开发人员的开发效率低,写的代码不易于维护和扩展的问题。