到initComponent()或不到initComponent()

时间:2013-01-24 01:00:31

标签: extjs extjs4 extjs4.1

我在ExtJS 4中构建一个应用程序时很困难,其中一部分是关于何时在initComponent()中配置某些内容以及什么时候不能... ...

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

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

我猜原因应该很明显,但我很想念它。有什么指针吗?

4 个答案:

答案 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.ControllerExt.app.Application

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

以这种方式看待它:如果你要编写旧式Web应用程序,在服务器端生成HTML和东西,你会尝试不让任何HTML与代码混合,是吗?左侧的模板,右侧的代码。这与initComponent中的硬编码数据几乎相同:确定它有效,直到某一点。然后它变成一碗意大利面条,难以维持和延伸。哦,测试一切!呸。

现在,当你需要在运行时使用实例执行某些操作时,次,而不是类定义时间 - 经典示例是应用事件侦听器,或在控制器中调用control。您必须从对象实例中获取实际的函数引用,并且必须在initComponentinit中执行此操作。但是,我们正在努力缓解这个问题 - 应该没有硬性要求对所有这些进行硬编码;不久,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之间注入你的代码,你可以在你要扩展的类的构造函数中查找它。