45

我在 ExtJS 4 中构建应用程序时遇到了困难,其中一部分是混淆了何时在 initComponent() 中配置某些内容以及何时不...

例如,在 Sencha 自己的MVC Application Architecture文档中,当第一次创建网格视图时,他们在 initComponent() 方法中定义了内联存储。(参见“定义视图”部分)

再往下,当他们将 store 分解为一个单独的类时,他们将定义移到了 initComponent() 之外。有一个有用的评论提请注意这一事实,但没有解释。(请参阅创建模型和存储部分)

我想原因应该很明显,但我错过了。任何指针?

4

4 回答 4

44

如果您对 ExtJS 类系统的工作方式没有深入了解,您可能需要遵循以下内容:

在 中声明所有非原始类型initComponent()

术语

  • 原始类型- 字符串、布尔值、整数等。
  • 非原语- 数组和对象。

解释

如果要多次创建您扩展的组件,则声明为配置选项(外部initComponent)的任何非原始配置将在所有实例之间共享。

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

这种行为在下面 sra 的回答和这篇 Skirtle's Den 文章中进行了解释。您可能还想阅读这个 SO question

于 2013-01-24T02:08:57.200 回答
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例子被简化了,解决配置并不总是那么容易。

结论

总是把你的类定义写成配置!除了普通配置之外,它们不得包含任何引用的实例,并且必须在创建实例时处理这些实例以解决它们。

免责声明

我并没有声称用这篇非常简短的文章来涵盖所有内容!

于 2013-01-26T09:29:52.577 回答
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 本身缺乏的东西:编译时阶段。它为我们提供了框架代码本身利用的大量可能性;Ext.app.Controller如果您想要一些示例,请查看Ext.app.Application最新的 4.2 beta。

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

这样看:如果您要编写一个老式 Web 应用程序,在服务器端生成 HTML 和其他内容,您会尽量不将任何 HTML 与代码混合,对吗?左边是模板,右边是代码。这实际上与硬编码数据相同initComponent:在一定程度上,它确实有效。然后它就变成了一碗意大利面,难以维护和扩展。哦,测试所有这些!呸。

现在,有时您需要在运行时对实例做一些事情,而不是在类定义时间 - 经典示例是应用事件侦听器或调用control控制器。您必须从对象实例中获取实际的函数引用,并且您必须在initComponentor中执行此操作init。然而,我们正在努力解决这个问题——应该没有硬编码所有这些的硬性要求;Observable.on()已经支持字符串侦听器名称,MVC 内容也将很快支持。

正如我在上面的评论中所说,我将不得不为文档写一篇文章或指南,解释事情。那可能要等到 4.2 发布;同时,希望这个答案应该对此事有所启发。

于 2013-01-24T01:17:21.390 回答
12

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

很高兴知道类配置选项对象是共享的,您需要为每个实例初始化/处理它们,但是代码可以进入构造函数以及 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 之间注入你的代码,你可以在你要扩展的类的构造函数中查找。

于 2013-05-01T11:29:16.730 回答