我在ExtJS 4中构建一个应用程序时很困难,其中一部分是关于何时在initComponent()中配置某些内容以及什么时候不能... ...
例如,在Sencha自己的MVC Application Architecture doc中,首次创建网格视图时,他们在initComponent()方法中定义了内联存储。 (参见“定义视图”部分)
再往下,当他们将商店分解为一个单独的类时,他们将定义移到了initComponent()之外。有一个有用的评论引起了对这一事实的关注,但没有解释。 (请参阅创建模型和存储部分)
我猜原因应该很明显,但我很想念它。有什么指针吗?
答案 0 :(得分:44)
如果您对ExtJS类系统的工作原理不是很了解,可能需要遵循以下步骤:
在
initComponent()
中声明所有非基本类型。
如果您要扩展的组件不止一次被创建,那么任何声明为配置选项(在initComponent
之外)的非原始配置都将在所有实例之间共享。
因此,当在多个选项卡上创建扩展组件(通常是扩展网格)时,许多人遇到了问题。
下面和this Skirtle's Den article中的sra答案中解释了这种行为。您可能还想阅读this SO question。
答案 1 :(得分:22)
首先,我会对我的评论采取立场:
@AlexanderTokarev别误会我的意思。我没有谈论组件的配置,或更糟糕的实例并将它们移到initComponent()
,这不是我的观点。
现在我对此有何看法。
initComponent()应解决实例所需的任何事情 这个类的创建。不多也不少。
你可以在定义类时搞乱一个负载,并且大部分都是因为人们不理解ExtJS类系统的工作原理。由于这是关于组件,以下将集中于那些。它也是一个简化的例子,它应该只显示我已经看过很多次的错误。
让我们开始吧:我们有一个自定义面板,可以做很多漂亮的事情。这带来了自定义配置的需求,我们称之为foo
。我们将它和我们的默认配置选项一起添加到类定义中,以便我们可以访问它:
Ext.define('Custom', {
extend: 'Ext.panel.Panel',
alias: 'widget.custpanel',
foo: {
bar: null
},
initComponent: function() {
this.callParent(arguments);
}
});
但测试后事情变得奇怪了。我们的配置值似乎神奇地改变了。这是 JSFiddle
发生的事情是所有创建的实例都引用相同的foo
实例。但最近我做到了
store: {
fields: [ ... ],
proxy: {
type: 'direct',
directFn: 'Direct.Store.getData'
}
}
有一家商店并且有效。那么为什么foo
不起作用?
大多数人认为这个小foo
对象和(ExtJS)配置之间没有任何区别,这基本上是正确的,因为它们都是对象(实例)。但不同之处在于,sencha发布的所有类都非常清楚他们期望的配置属性并负责处理它们。
例如,网格的商店属性由StoreManager
解析,因此可以是:
storeId
字符串或在网格初始化期间,其中任何一个都会被实际的商店实例解析和覆盖。商店只是一个例子。我想更为人所知的是items数组。这是一个定义时的数组,并且使用MixedCollection为每个实例覆盖它(如果我没有记错的话)。
是的,类定义与从中创建的实例之间存在差异。但是我们需要处理任何包含上述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
现在我们在创建实例时处理foo
配置。现在,这个foo
示例已经过简化,解析配置并不总是那么容易。
<强>结论强>
始终将您的类定义编写为配置!他们一定不能 包含任何引用的实例,除了普通配置和必须 在创建实例时,请注意这些以解决它们。
<强>声明强>
我并没有声称用这个非常简短的写作来涵盖所有内容!
答案 2 :(得分:14)
我通常主张在类配置选项中尽可能多地配置,因为它读取更好并且更容易在子类中重写。除此之外,很有可能在未来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'
});
请注意这些方法有何不同。在第一个例子中,我们进行了很多硬编码:对象属性值,存储配置,使用时的MyPanel类名;我们实际上是在扼杀一个阶级的想法,因为它变得难以理解。在第二个示例中,我们正在创建一个模板,可以重复使用多次,可能有不同的配置 - 基本上,这就是整个类系统的内容。
但实际差异更深。在第一种情况下,我们有效地将类配置推迟到运行时,而在第二种情况下,我们定义类配置并且在非常不同的阶段应用它。实际上,我们可以很容易地说第二种方法引入了JavaScript本身缺乏的东西:编译时间阶段。它为我们提供了大量的可能性,这些可能性在框架代码本身中被利用;如果您需要一些示例,请查看最新4.2测试版中的Ext.app.Controller
和Ext.app.Application
。
从更实际的角度来看,第二种方法更好,因为它更容易阅读和处理。一旦你掌握了这个想法,你就会发现自己编写了所有代码,因为这样更简单。
以这种方式看待它:如果你要编写旧式Web应用程序,在服务器端生成HTML和东西,你会尝试不让任何HTML与代码混合,是吗?左侧的模板,右侧的代码。这与initComponent
中的硬编码数据几乎相同:确定它有效,直到某一点。然后它变成一碗意大利面条,难以维持和延伸。哦,测试一切!呸。
现在,当你需要在运行时使用实例执行某些操作时,是次,而不是类定义时间 - 经典示例是应用事件侦听器,或在控制器中调用control
。您必须从对象实例中获取实际的函数引用,并且必须在initComponent
或init
中执行此操作。但是,我们正在努力缓解这个问题 - 应该没有硬性要求对所有这些进行硬编码;不久,Observable.on()
已经支持字符串监听器名称和MVC内容。
正如我在上面的评论中所说,我将不得不为文档撰写文章或指南,解释事情。那可能要等到4.2发布;同时,这个答案应该对这个问题有所启发,希望如此。
答案 3 :(得分:12)
当我到达这里时,我一直在寻找同一问题的答案,看到这些答案让我很失望。这些都没有回答这个问题:initComponent()或构造函数?
很高兴知道类配置选项对象是共享的,您需要为每个实例初始化/处理它们,但代码可以进入构造函数以及initComponent()函数。
我的猜测是Component类的构造函数在中间的某个地方调用initComponent()而且我没有错:只需查看源代码,它实际上是{{3} }。
所以它看起来像这样:
AbstractComponent/ctor:
- stuffBeforeIC()
- initComponent()
- stuffAfterIC()
现在,如果你扩展一个Component,你将会得到这样的结果:
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之间注入你的代码,你可以在你要扩展的类的构造函数中查找它。