- Ember中视图与组件的对比
- 创建一个不在视图树中的任意视图,如对话框
- 时序:didInsertElement和Em.run区别与各自应用场景
1 Ember中视图与组件的对比
依据现有经验,结合国外社区的讨论,总结两者的对比:
从最终奥义来讲,视图能实现当前应用内的代码复用,而组件则能实现应用无关的放之任何场景都可用的代码复用
由于两者意图的不同,视图代码会更贴合当前业务需求,但不利于独立成一个日后可用的工具箱部件。而组件则会极大的与当前业务需求解耦出来,它仅提供几个有限的对外接口
视图的上下文是和当前所在路由控制器一致的,也就是共用了一套变量环境,并往往会与外部环境的控制器,消息传递发生交叉
组件的上下文则是与外部上下文独立的(当然,如有必要,组件依赖某些外部变量,还是能通过属性传入的方法来导入)。另外它也不能访问外部环境的控制器。如果需要消息交互,则应该通过一个主操作接口来向外部环境传出消息。
总体来说,很多方面,组件都力图做到内外部的解耦,充分的独立。虽然这么做能极方便的使其成为我们日后可用工具箱的一个部件,但是由于较大的脱离了业务逻辑以及上下环境的隔离,其实现往往需要更多的代码,许多时候,还需要我们处理一些细节来绕过其特性带来的约束。
2 创建一个不在视图树中的任意视图,如对话框
在Ember应用中,那些通过路由管理,容器视图管理,视图助手管理等等的视图,都是构建在一个完整视图树层级中的,并且能被chrome浏览器中的Ember Ispector拓展检测到的
然而有时候我们可能会需要创建一个不在视图树层级中的视图,某些场景下这往往能带来逻辑实现上的方便
例如一个对话框,官网教程cookbook上对于对话框的实现涉及了outlet插口,路由actions,而且这还是个未集成上效果美观的jQuery对话框插件的初步效果,咋看起来实现上还是略略麻烦,且将UI的操作放进路由里处理不太符合MVC的概念,这种界面上的响应操作我推荐写进视图的actions中
那么讲讲我推荐的做法,首先我们可以看到,一个对话框在应用中往往没有明确的节点位置、视图层级关系,那么我们可以构建一个不在视图树层次中的视图来实现它,同时我们可以手动指定一个控制器给它以提供合适的上下文环境,需要注意的是我们必须传入一个变量容器container给它(否则控制台会报出一个逆反信息)。最后,对于一个无层级的视图,我们需要通过调用视图的append方法将其追加到body节点中:
App.DialogView.create({ controller: XXXX, container: this.container, }).append();
ok, 当视图插入到body中后,也就是说dialog的主体dom内容已经ready了,接下去我们需要通过一个jQuery对话框插件将其弹出。最后,由于对话框关闭时,仅仅是通过js的方式将其css式隐藏而没有销毁对话框的视图对象,为了避免下次弹出对话框时重复,我们这里需要手动地在对话框关闭时候,将这个对话框视图销毁掉:
App.DialogView.extend = Em.View.extend({ didInsertElement: function(){ this._super(); var self = this; this.$().dialog({ height: 200, width: 500, draggable: true, resizable: false, modal: true, title: "请选择竞争车系......", close: function(event, ui){ self.destroy(); }, } });
ok,最后还要提醒的是,由于这个对话框是不在视图层级的,所以Ember Ispector中调试时候,我们是观察不到它的,我们需要手动的在console中输出调试信息或者加debug断点来测试它。
3 时序问题:didInsertElement和Em.run的区别与各自应用场景
Ember提供了两套逻辑来对应用生命周期的各个时间点进行管理.
1)通过生命周期钩子对一个视图view的生命周期进行管理.
包括了willInsertElement、didInsertElement、willDestroyElement、willClearRender、becameVisible、becameHidden六个视图层次的生命周期钩子.
2)通过运行时循环对应用的一个事件响应周期进行管理.
包括了sync, actions, routerTransitions, render, afterRender, destroy六个运行时队列
通常的,我们用的较多的分别是didInsertElement钩子与afterRender运行时队列。
在didInsertElement中的操作确保了当前视图及其父视图已经ready,但是不能确保其子视图的ready。
而afterRender运行时队列确保了应用当前所有的运行视图已经ready。
举例一个应用场景,如商品列表页面上有一组筛选项,它的结构是一个大容器视图包含了许多个筛选项视图,我们希望在筛选项都渲染出来后,进行一个初始化操作,将部分筛选项临时收拉起来。
首先我们就发现不能在大容器视图进入didInsertElement钩子即容器视图ready后进行初始化操作,因为此时筛选项作为其子视图还没有ready。那么最后,我们其实可以在大容器视图的didInsertElement钩子中调度一个afterRender运行时队列,这样就确保了大容器及筛选项视图的ready,并进一步的进行初始化操作:
App.FiltersContainerView = Em.View.extend({ didInsertElement: function(){ this._super(); Em.run.afterRender(‘afterRender‘, this, function(){ #初始化操作,通过调度afterRender队列来等待子筛选项视图的ready }); } });