initComponent()或不initComponent()

当我在ExtJS 4中构build一个应用程序的时候,我感到很困惑,部分原因是在initComponent()中什么时候configuration什么以及什么时候不需要…

例如,在Sencha自己的MVC应用程序体系结构文档中,当首次创build网格视图时,他们在initComponent()方法中定义了内联存储。 (请参阅“定义视图”部分)

更进一步,当他们将商店分解成一个单独的类时,他们将定义移到了initComponent()之外。 有一个有用的评论,提请注意这一事实,但没有解释。 (请参阅创build模型和存储部分)

我猜这个理由应该是很明显的,但是我错过了。 任何指针?

如果您对ExtJS类系统的工作原理没有深刻的理解,那么您可能需要遵循以下内容:

initComponent()声明所有非基元types。

术语

  • 原始types – string,布尔值,整数等
  • 非基元 – 数组和对象。

说明

如果你扩展的组件不止一次被创build,任何声明为configuration选项( initComponent之外)的非原始configuration都将在所有实例之间共享。

因此,当在多个选项卡上创build扩展组件(通常是扩展网格)时,许多人遇到问题。

这个行为在sra的回答下面和这个Skirtle的Den文章中有解释 。 你可能也想阅读这个问题 。

首先我会对我的评论表态:

@AlexanderTokarev不要误解我的意思。 我不谈论组件的configuration,或更糟糕的实例,并将它们移动到initComponent() ,这不是我的观点。

现在我想到了这个。

initComponent()应该解决创build这个类的实例所需的任何东西。 没有更多,不能less。

定义类时,可能会弄乱一些负载,而大部分情况是因为人们不了解ExtJS类系统如何工作。 由于这是关于组件,下面将重点介绍这些组件。 这也将是一个简单的例子,应该只显示我曾经见过很多次的错误。

让我们开始吧:我们有一个自定义面板,可以做很多漂亮的事情。 这提出了一个自定义configuration的需要,我们称之为foo 。 我们将它和我们的默认configuration选项一起添加到类定义中,以便我们可以访问它:

 Ext.define('Custom', { extend: 'Ext.panel.Panel', alias: 'widget.custpanel', foo: { bar: null }, initComponent: function() { this.callParent(arguments); } }); 

但是testing之后,事情变得怪异。 我们的configuration的价值似乎神奇地改变。 这里是一个JSFiddle发生了什么事情是所有创build的实例引用相同的foo实例。 但最近我做到了

 store: { fields: [ ... ], proxy: { type: 'direct', directFn: 'Direct.Store.getData' } } 

与一家商店和那工作。 那么为什么不工作?

大多数人没有看到这个小foo对象和(ExtJS)configuration之间的任何区别,基本上是正确的,因为两者都是对象(实例)。 但不同之处在于,sencha发运的所有类都非常了解他们期望的configuration属性并对其进行处理。

例如,网格的商店属性由StoreManagerparsing,因此可以是:

  • storeIdstring,或
  • 存储实例或
  • 存储configuration对象。

在网格的初始化过程中,这些中的任何一个都会被实际的商店实例parsing和覆盖。 商店只是一个例子。 我猜更知名的是项目数组。 这是定义时间的一个数组,如果我没有弄错的话,它会被混合聚集(MixedCollection)的每个实例覆盖。

是的,类定义和从中创build的实例是有区别的。 但是,我们需要照顾任何新的财产,其中包含像上面的foo这样的参考,而且这并不复杂。 这是我们需要做的,以修复它的foo例子

 Ext.define('Custom', { extend: 'Ext.panel.Panel', alias: 'widget.custpanel', foo: { bar: null }, initComponent: function() { this.foo = Ext.apply({}, this.foo); this.callParent(arguments); } }); 

这是JSFiddle

现在我们在创build实例时关心fooconfiguration。 现在这个例子被简化了,并不总是很容易解决一个configuration。

结论

总是把你的class级定义写成configuration! 除了普通configuration之外,它们不得包含任何引用的实例,并且必须负责在创build实例时解决这些问题。

放弃

我并不是说这个真的很简短,

我通常主张在类configuration选项中尽可能多的configuration,因为它读得更好,更容易在子类中覆盖。 除此之外,很有可能在将来Sencha Cmd将优化编译器,所以如果你保持你的代码声明,它可以从优化中受益。

比较:

 Ext.define('MyPanel', { extend: 'Ext.grid.Panel', initComponent: function() { this.callParent(); this.store = new Ext.data.Store({ fields: [ ... ], proxy: { type: 'direct', directFn: Direct.Store.getData } }); this.foo = 'bar'; } }); ... var panel = new MyPanel(); 

和:

 Ext.define('MyPanel', { extend: 'Ext.grid.Panel', alias: 'widget.mypanel', foo: 'bar', store: { fields: [ ... ], proxy: { type: 'direct', directFn: 'Direct.Store.getData' } } }); ... var panel = Ext.widget({ xtype: 'mypanel', foo: 'baz' }); 

请注意这些方法是如何非常不同的。 在第一个例子中,我们使用了很多硬编码方式:对象属性值,存储configuration,MyPanel类名, 我们实际上正在杀死一个class级的想法,因为它变得不可能。 在第二个例子中,我们创build了一个模板 ,可以通过不同的configuration多次重复使用 – 基本上,这就是整个class级系统的内容。

然而,实际的差异更深。 在第一种情况下,我们实际上将类的configuration推迟到了运行时,而在第二种情况下,我们正在定义类的configuration并将其应用于非常不同的阶段。 事实上,我们可以很容易地说第二种方法引入了JavaScript本身缺乏的东西:编译时间阶段。 它给了我们在框架代码本身被利用的大量可能性; 如果你想要一些例子,请看最新的4.2testing版中的Ext.app.ControllerExt.app.Application

从更实际的angular度来看,第二种方法更好,因为它更容易阅读和处理。 一旦你掌握了这个想法,你就会发现自己编写了所有的代码,因为这样做更简单。

看看这样:如果你要编写一个老式的Web应用程序,在服务器端生成HTML和东西,你会试着不要有任何HTML与代码混合,你会吗? 左边的模板,右边的代码。 这和initComponent数据硬编码几乎是一样的:确保它可以正常工作。 然后它变成一碗意大利面,很难保持和延伸。 哦,testing所有的! 呸。

现在,有些时候你需要在运行时用一个实例做一些事情,而不是类定义时间 – 经典的例子是应用事件监听器,或者在控制器中调用control 。 您将不得不从对象实例中获取实际的函数引用,并且必须在initComponentinit 。 但是,我们正在努力解决这个问题 – 不应该硬编码所有这些; Observable.on()已经支持string监听器名称,MVC的东西也将很快。

正如我在上面的评论中所说的那样,我将不得不为文档撰写文章或指南,解释事情。 那可能要等到4.2才发布。 同时希望这个答案能够说明问题。

当我在这里结束时,我一直在寻找同样问题的答案,看到这些答案让我感到失望。 这些都没有回答这个问题:initComponent()或构造函数?

很高兴知道类configuration选项对象是共享的,并且您需要为每个实例初始化/处理它们,但代码可以进入构造函数以及initComponent()函数。

我的猜测是,Component类的构造函数调用了initComponent()中间的某个地方,我不是很错:只需要看源代码,它实际上就是AbstractComponent的构造函数 。

所以看起来像这样:

 AbstractComponent/ctor: - stuffBeforeIC() - initComponent() - stuffAfterIC() 

现在,如果你扩展一个组件,你会得到这样的东西:

 constructor: function () { yourStuffBefore(); this.callParent(arguments); yourStuffAfter(); }, initComponent: function () { this.callParent(); yourInitComp() } 

这些被调用的最终订单是:

 - yourStuffBefore() - base's ctor by callParent: - stuffBeforeIC() - initComponent: - base's initComponent by callParent - yourInitComp() - stuffAfterIC() - yourStuffAfter() 

所以最终都取决于你是否需要在stuffBeforeIC和stuffAfterIC之间注入代码,你可以在你要扩展的类的构造函数中查找它们。