Ext JS 提供了一些令人兴奋的改进,您可以在您的应用程序中使用它们。我们增加了的ViewModels(视图模型)和ViewControllers(视图控制器),将原有的架构从MVC升级到了MVVM。最重要的是,这些功能不会相互影响,所以您可以逐步引入它们,直到它们融为一体。
应用级控制器
一个控制器派生于Ext.app.Controller。这些控制器使用CSS类选择器(“组件查询”)来匹配组件和它们的事件。 他们还利用“refs”来选择和检索组件实例。
这些控制器在应用程序启动时就创建并且一直存在,直到应用程序关闭。在其生命周期中,控制器可以监听一个或者多个视图的事件,并且管理它们。
不足之处
对于大型应用程序,这些技术有一定的不足之处。
在开发时,视图和控制器可以由多个开发人员撰写,并集成到最终的应用程序中。很难保证控制器能达到开发者的预期效果。另外,开发者通常希望限制在应用程序中控制器的数量。然而一些控制器即使不再被使用,也不能销毁它们。
视图控制器(ViewControllers)
Ext JS 5+在能够向后兼容传统应用级控制器的同时,它引入了新的控制器来应对这些不足之处:Ext.app.ViewController。 视图控制器做到了以下几点:
- 在视图中使用了“listeners”和“reference”简化了配置。
- 充分利用视图的生命周期来自动管理其相关的视图控制器。
- 视图控制器与视图的一对一的关系降低了应用的复杂性。
- 视图控制器提供的封装使嵌套视图更加可靠。
- 保留选择组件,并具有在相关的视图下任何级别监听它们的事件的能力。
监听器(listeners)
那么如何使用视图控制器呢,我们可以看看下面两个例子。第一个是在视图的子项中使用监听器监听事件的基本用法:
Ext.define('MyApp.view.foo.Foo', {
extend: 'Ext.panel.Panel',
xtype: 'foo',
controller: 'foo',
items: [{
xtype: 'textfield',
fieldLabel: 'Bar',
listeners: {
change: 'onBarChange' // 这里没有指定作用域
}
}]
});
Ext.define('MyApp.view.foo.FooController', {
extend: 'Ext.app.ViewController',
alias: 'controller.foo',
onBarChange: function (barTextField) {
// 通过 'change' 事件触发
}
});
上面的例子展示了如何监听事件来触发指定方法(onBarChange
),并且不用指定其scope(作用域)。因为视图控制器的内部事件系统已经指定了scope。
在之前的版本中,监听器配置被保留,以供组件的创建者使用,那么如何监听一个视图的事件,或者销毁其父类?答案是,我们需要指定一个scope:
Ext.define('MyApp.view.foo.Foo', {
extend: 'Ext.panel.Panel',
xtype: 'foo',
controller: 'foo',
listeners: {
collapse: 'onCollapse',
scope: 'controller' // 这里指定了作用域
},
items: [{
...
}]
});
上面的例子充分利用了Ext JS的两个新功能:作用域(scope)和监听器。 在这里我们重点关注作用域配置,作用域的值可以设置为: “this” 和 “controller”。 在编写MVC架构的应用程序时,我们总是使用“controller”来监听视图中的事件。
由于视图属于 Ext.Component类型,在其他视图中我们通过“xtype”创建了一个实例来使用这个视图,同时我们用同样的方式在这个视图中创建了一个文本框。 我们是如何使用的呢,看下面的例子:
Ext.define('MyApp.view.bar.Bar', {
extend: 'Ext.panel.Panel',
xtype: 'bar',
controller: 'bar',
items: [{
xtype: 'foo',
listeners: {
collapse: 'onCollapse'
}
}]
});
在这种情况下,bar视图创建一个实例Foo作为其子视图,再进一步,它监听折叠事件就像Foo视图一样。当这个事件在bar视图的视图控制器触发时这个事件也会在Foo视图的视图控制器里触发。
Reference
ViewControllers最常见的用法是监听组件执行的特定的操作,例如:
Ext.define('MyApp.view.foo.Foo', {
extend: 'Ext.panel.Panel',
xtype: 'foo',
controller: 'foo',
tbar: [{
xtype: 'button',
text: 'Add',
handler: 'onAdd'
}],
items: [{
xtype: 'grid',
...
}]
});
Ext.define('MyApp.view.foo.FooController', {
extend: 'Ext.app.ViewController',
alias: 'controller.foo',
onAdd: function () {
// ... 获取表格组件并且添加一条数据 ...
}
});
但是应该如何获得表格组件?所有的技术都需要您把一些可识别的唯一标识属性配置在表格中。旧技术用“id”配置(Ext.getCmp获取)或者“itemId”配置(用“refs” 或者其他组件检索方法)。“id”的优势是首先查找,但由于这些标识符在整个应用程序和DOM中都必须是唯一的,这通常是不可取的。使用“itemId”和某种形式的查询更加灵活,但有必要执行搜索才能找到所需的组件。
用reference
配置,添加“reference”配置到表格并用lookupReference
方法获取表格组件:
Ext.define('MyApp.view.foo.Foo', {
extend: 'Ext.panel.Panel',
xtype: 'foo',
controller: 'foo',
tbar: [{
xtype: 'button',
text: 'Add',
handler: 'onAdd'
}],
items: [{
xtype: 'grid',
reference: 'fooGrid'
...
}]
});
Ext.define('MyApp.view.foo.FooController', {
extend: 'Ext.app.ViewController',
alias: 'controller.foo',
onAdd: function () {
var grid = this.lookupReference('fooGrid');
}
});
这类似于使用itemId“fooGrid”时做的事情:“this.down(‘#fooGrid’)”,但是,它们使用的查找引擎是有区别的,首先,reference配置指示组件注册本身拥有的视图(视图控制器存在这种情况下)。第二,lookupReference
方法简单的查询缓存,以确定是否需要刷新(比如添加或删除一个容器)。如果一切正常,只是从缓存中返回引用。如同在伪代码中:
lookupReference: (reference) {
var cache = this.references;
if (!cache) {
Ext.fixReferences(); // 修复引用
cache = this.references; // 缓存引用
}
return cache[reference];
}
换句话说,只要不删除或者添加条目,容器是固定的,没必要去搜索。下面我们将看到这种方法的效率之处。
封装
在控制器使用refs
配置选择器非常灵活,但同时有一定风险。这些选择器能够监听所有层次结构中的组件,这是是强大的也是容易出错的。例如,当控制器隔离工作时100%可用,但当被其他视图引用时会失败,因为它的选择器与新视图不匹配。
这些问题可以通过以下办法来解决,在视图中使用监听器和references然后通过视图控制器监听,这些问题就不会出现了。因为监听器和references仅连接到自己的视图控制器。视图可以自由设置reference的名称,在这个视图范围内它是独一无二的,这些名称不会暴露视图的创造者。
同样,监听器在它们对应的视图控制器中处理,不会被其它的控制器影响。监听器是更可靠的选择器,这两种机制在这些情况下的能够很好地协作,是理想的基本选择器方法。
要触发这个模型、视图需要触发的事件,可以用它们对应视图所属的视图控制器的一个辅助方法:fireViewEvent
。例如:
Ext.define('MyApp.view.foo.FooController', {
extend: 'Ext.app.ViewController',
alias: 'controller.foo',
onAdd: function () {
var record = new MyApp.model.Thing();
var grid = this.lookupReference('fooGrid');
grid.store.add(record);
this.fireViewEvent('addrecord', this, record);
}
});
视图中使用监听器的标准作法:
Ext.define('MyApp.view.bar.Bar', {
extend: 'Ext.panel.Panel',
xtype: 'bar',
controller: 'bar',
items: [{
xtype: 'foo',
listeners: {
collapse: 'onCollapse',
addrecord: 'onAddRecord'
}
}]
});
监听器(Listeners)和事件域
在 Ext JS 4.2 中, MVC事件调度器是广义的活动领域,这些事件域通过它们的控制器控制选择器匹配监听事件触发,“component”事件域有完整的组件查询选择器,在其他域有更多的选择器。
在Ext JS 5+,每个视图控制器从一个叫“view”事件域创建新的实例,这些事件域允许视图控制器使用标准的listen
和control
方法,同时限制它们的视图隐式范围。它还增加了一个特殊选择器,以匹配视图本身:
Ext.define('MyApp.view.foo.FooController', {
extend: 'Ext.app.ViewController',
alias: 'controller.foo',
control: {
'#': { // 匹配视图本身
collapse: 'onCollapse'
},
button: {
click: 'onAnyButtonClick'
}
}
});
监听器和选择器之间的关键差异请看上面的代码,“button”选择器将匹配这个视图的和子视图的所有按钮,不管深度,即使那个按钮属于一个曾孙视图。换句话说,基本选择器处理程序不管封装边界。这些行为和以前的Ext.app.Controller 行为一致,在有限的情况下是一个有用的技术。
最后,这些事件域在视图层结构遵循嵌套并且向上有效“bubble”,就是说,当一个事件被触发,它首先提交到标准事件事临听器,然后,提交到自己的视图控制器,再向上通过层次结构提交到父视图控制器(如果有)。最终,事件提交到标准“component”事件域被Ext.app.Controller处理。
生命周期
对于大型应用程序动态创建控制器是一种常见技术,这将有助于减少应用程序的加载时间和运行时不激活所有控制器以提高性能。Ext JS在之前的版本是有做不到的,一旦创建,这些控制器将活跃在整个应用程序的生命周期。不能摧毁他们释放资源。同样,一个控制器可以管理任意数量的相关视图(包括没有)。
然而,视图控制器在视图创建的早期就绑定到组件整个生命周期,视图销毁,视图控制器同样销毁。这意味着视图控制器不再管理许多视图或者不存在视图。
这种一对一的关系意味着reference是简化的,摧毁组件不再容易泄漏内存。视图控制器可以在其生命周期帮助您实现这些方法:
- beforeInit - 为了操作视图可以覆盖这个方法,它在initComponent方法被调用之前调用,在控制器创建后立即调用,在initConfig方法调用组件构造函数后触发。
- init - 视图的initComponent方法调用不久后调用,这是执行控制器初始化、视图初始化的典型时间。
- initViewModel - 当视图的视图模型创建时调用(如果有)。
- destroy - 清理和回收资源(必须调用
callParent
方法)
结论
我们认为视图控制器能极大地简化MVC程序,它们结合视图模型能够做的更好,您可以在开发中利用这些方法的优点来进行开发。我们很高兴能在您即将发布的应用程序,看到这些改进。