原文:Declarative Listeners in Ext JS 5
在前文《在Ext JS 5使用ViewControllers》中,简单的介绍了Ext JS 5的一项重要改进——声明式事件监听。在本文,将深度探讨如何使用声明式事件监听啦简化应用程序的视图并减少自定义组件的样板代码。
注意:文章假设你使用的是Ext JS 5.0.1或更高版本。
什么是声明式事件监听?
所谓的“声明式事件监听”,就是指定义在类主体中的监听或在实例的配置对象中使用了listeners配置项。以这种方式来声明事件监听不是Ext JS 5的新功能。在Ext JS 4,可以正在类中声明事件监听,不过只适于处理函数或作用域已定义的情况,例如:
Ext.define('MyApp.view.User', { extend: 'Ext.panel.Panel', listeners: { // 函数必须内联或在之前已被定义: collapse: function() { // respond to panel collapse here } }, //该方法不能被定义为collapse的处理函数: onCollapse: function() { } });
由于所需的处理函数不能使用类中定义的方法,因而声明式监听在Ext JS 4中的使用有限。开发人员通常会通过重写initComponent方法并使用on方法来添加监听:
Ext.define('MyApp.view.User', { extend: 'Ext.panel.Panel', initComponent: function() { this.callParent(); this.on({ collapse: this.onCollapse, scope: this }); }, onCollapse: function() { console.log(this); // the panel instance } });
作用域解析
在Ext JS 5,对listeners配置项做了改进,允许将事件处理指定为字符串来对应方法名。在运行时(触发事件的任何时候),框架会将这些方面解析为实际的函数引用。我们将这一过程称为事件监听作用域解析。
在Ext JS 4,如果明确给出了“作用域”,才能解析字符串处理程序。而在Ext JS 5,在声明“字符串”处理程序而没有明确声明作用域的时候,为默认作用域解析添加了一些特殊规则。
作用域解析有两种结果:组件或视图控制器(ViewController)。无论是哪种结果,都会从组件开始搜索。作用域可能是组件,也可能是视图控制器,如果不是,框架会“爬”到组件的上层直到找到适合的组件或视图控制器。
解析作用域为组件
框架解析作用域的第一种方式是寻找defaultListenerScope配置项为true的组件。对于类中的事件监听声明,搜索会从组件自身开始。
Ext.define('MyApp.view.user.User', { extend: 'Ext.panel.Panel', xtype: 'user', defaultListenerScope: true, listeners: { save: 'onUserSave' }, onUserSave: function() { console.log('user saved'); } });
监听被定义在了用户视图的类主体,这意味着框架在提升层次之前会先检查用户视图自身的defaultListenerScope。在当前示例,用户视图将defaultListenerScope设置为了true,那当前监听的作用域将会被解析为用户视图。
对于事件监听被声明在实例配置项的情况,将会条过组件自身,框架会从父容器开始搜索,请参考以下代码:
Ext.define('MyApp.view.main.Main', { extend: 'Ext.container.Container', defaultListenerScope: true, items: [{ xtype: 'user', listeners: { remove: 'onUserRemove' } }], onUserRemove: function() { console.log('user removed'); } });
对于用户视图的监听是在实例的配置对象中声明的,这意味着框架会跳过用户视图(尽管它定义了defaultListenerScope为true),且会解析为主视图。
解析作用域为视图控制器
在Ext JS 5,引入了新的控制器类型——Ext.app.ViewController。在《在Ext JS 5使用ViewControllers》中详细介绍了视图控制器,因此这里只讨论与视图控制器与事件监听有关的部分。
与Ext.app.Controller可以管理许多视图不同,每一个视图控制器实例只能绑定一个视图实例。视图与视图控制器之间之间一对一的关系允许视图控制器作为视图或视图的条目中事件监听声明的默认作用域。
对于defaultListenerScope,规则同样适用于视图控制器。类层的监听总是会在搜索组件的上层之前先搜索组件自身的视图控制器。
Ext.define('MyApp.view.user.User', { extend: 'Ext.panel.Panel', controller: 'user', xtype: 'user', listeners: { save: 'onUserSave' } }); Ext.define('MyApp.view.user.UserController', { extend: 'Ext.app.ViewController', alias: 'controller.user', onUserSave: function() { console.log('user saved'); } });
上述监听被声明在用户视图的类主体内,由于用户视图有它自己的控制器,框架会解析作用域为UserController。如果用户视图没有自己的控制器,那么作用域会解析到上层。
另一方面,实例层监听会跳过组件并解析为视图控制器上层的父容器,例如:
Ext.define('MyApp.view.main.Main', { extend: 'Ext.container.Container', controller: 'main', items: [{ xtype: 'user', listeners: { remove: 'onUserRemove' } }] }); Ext.define('MyApp.view.main.MainController', { extend: 'Ext.app.ViewController', alias: 'controller.main', onUserRemove: function() { console.log('user removed'); } });
合并listeners配置项
在Ext JS 4,在基类声明的监听会被子类或实例的listeners配置项的声明完全重写。在Ext JS 5,改进了listeners的API,可适当的合并在基类、子类和实例中的事件监听声明。要想了解其中的行为,可查看以下示例:
Ext.define('BaseClass', { extend: 'Ext.Component', listeners: { foo: function() { console.log('foo fired'); } } }); Ext.define('SubClass', { extend: 'BaseClass', listeners: { bar: function() { console.log('bar fired'); } } }); var instance = new SubClass({ listeners: { baz: function() { console.log('baz fired'); } } }); instance.fireEvent('foo'); instance.fireEvent('bar'); instance.fireEvent('baz');
在Ext JS 4,上面示例只会输出“baz”,但在Ext JS 5,listeners配置项会被正确的合并并输出“foo bar baz”。这就允许类在需要的时候才去声明监听而不需要知道超类是否已经有了监听。
小结
我们任务声明式的监听可大大简化应用程序中的事件监听定义。结合视图控制器用于处理应用程序的逻辑和视图模型的双向绑定,还可以尽可能的改进应用程序的开发体验。尝试去让我们知道你的想法。
作者:Phil Guerrant
Phil is a Sencha software engineer who works on Ext JS. He has over 10 years of experience as a developer and specializes in HTML5 and web development, UI, and agile methodologies.