到initComponent()或不到initComponent()

rom*_*afe 45 extjs extjs4 extjs4.1

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

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

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

我猜原因应该是显而易见的,但我很想念它.有什么指针吗?

Izh*_*aki 44

如果您对ExtJS类系统的工作方式不是很了解,您可能需要遵循以下步骤:

声明所有非基本类型initComponent().

术语

  • 原始类型 - 字符串,布尔值,整数等.
  • 非基元 - 数组和对象.

说明

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

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

这个行为在sra的答案和下面的Skirtle's Den文章中有解释.您可能还想阅读此SO问题.

  • 您的回答未能明确表明您的个人愿景违背了最佳做法和Sencha建议.另外,我建议你重新阅读Skirtle的文章,因为在我看来你完全错了.他实际上是在使用initComponent推荐*反对*,除非它是合理的. (4认同)
  • @AlexanderTokarev我认为没有任何理由可以解决这个问题.将非原始类型定义为属性,并且所有实例都将共享它(除非您正在对它进行操作)**但缺少的是**,声明不需要在`initComponent中严格完成它可以在任何通常由`constructor`或`initComponent`甚至任何其他方法调用的方法中完成.你可以把我的建议或最佳做法与我联系起来吗? (4认同)
  • @AlexanderTokarev,在这里,我们每两周左右就会得到一个带有非工作/错误代码的问题,其解决方案是将数组/对象配置移动到`initComponent()`.对我来说,知道我是否提供了错误的信息非常重要.你能否更好地解释为什么你认为不应该给出这个建议? (3认同)

sra*_*sra 22

首先,我将对我的评论采取立场:

@AlexanderTokarev别误会我的意思.我没有谈论组件的配置,或更糟糕的实例和移动它们initComponent(),这不是我的观点.

现在我对此有何看法.

initComponent()应解决创建此类实例所需的任何内容.不多也不少.

你可以在定义类时搞乱一个负载,其中大部分是因为人们不理解ExtJS类系统的工作方式.由于这是关于组件,以下将集中于那些.它也将是一个简化的例子,它应该只显示我已经看过很多次的错误.

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

Ext.define('Custom', {
    extend: 'Ext.panel.Panel',
    alias: 'widget.custpanel',

    foo: {
        bar: null  
    },

    initComponent: function() {
        this.callParent(arguments);
    }
});
Run Code Online (Sandbox Code Playgroud)

但测试后事情变得奇怪了.我们的配置值似乎神奇地改变了.这是一个JSFiddle 发生的事情是所有创建的实例都引用同一个foo实例.但最近我做到了

store: {
    fields: [ ... ],
    proxy: {
        type: 'direct',
        directFn: 'Direct.Store.getData'
    }
}
Run Code Online (Sandbox Code Playgroud)

与商店,这工作.那为什么不起作用foo

大多数人都没有看到这个小foo对象和(ExtJS)配置之间有任何区别,它基本上是正确的,因为它们都是对象(实例).但不同之处在于,sencha发布的所有类都非常清楚他们期望的配置属性并处理它们.

例如,网格的商店属性由the解析,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);
    }
});
Run Code Online (Sandbox Code Playgroud)

这是JSFiddle

现在我们在foo创建实例时处理配置.现在,这个foo示例已经过简化,解析配置并不总是那么容易.

结论

始终将您的类定义编写为配置!除了普通配置之外,它们不得包含任何引用的实例,并且必须在创建实例时处理这些实例以解决它们.

放弃

我并没有声称用这个非常简短的写作来涵盖所有内容!

  • @Izhaki感谢您的反馈.由于他们的开发团队成员大多总是说:**不要害怕源代码**我觉得这个话题很难掩盖,而且自4x以来,sencha已经迈出了巨大的一步,专注于新的MVC模式,性能和一些布局渲染错误.所以他们的大多数主题都涉及这些部分.还有其他一些重要的部分,比如event-delegate等.我告诉团队中的每个人都要查看源代码,它就在那里,API就是最好的之一. (3认同)

Ale*_*rev 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();
Run Code Online (Sandbox Code Playgroud)

和:

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'
});
Run Code Online (Sandbox Code Playgroud)

请注意这些方法是如何非常不同的.在第一个例子中,我们进行了很多硬编码:对象属性值,存储配置,使用时的MyPanel类名; 我们实际上是在扼杀一个阶级的想法,因为它变得难以理解.在第二个例子中,我们要创建一个模板,可以与可能不同的配置多次重复使用-基本上,这是全班系统是关于什么的.

但是,实际差异更深.在第一种情况下,我们有效地将类配置推迟到运行时,而在第二种情况下,我们定义类配置并在非常不同的阶段应用它.实际上,我们可以很容易地说第二种方法引入了JavaScript本身缺乏的东西:编译时间阶段.它为我们提供了大量的可能性,这些可能性在框架代码本身中被利用; 如果你想要一些例子,看一看Ext.app.Controller,并Ext.app.Application在最新的4.2测试版.

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

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

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

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


小智 12

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

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

我的猜测是Component类的构造函数在中间的某个地方调用initComponent()并且我没有错:只需查看源代码,它实际上是AbstractComponent的构造函数.

所以它看起来像这样:

AbstractComponent/ctor:
- stuffBeforeIC()
- initComponent()
- stuffAfterIC()
Run Code Online (Sandbox Code Playgroud)

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

constructor: function () {
  yourStuffBefore();
  this.callParent(arguments);
  yourStuffAfter();
},
initComponent: function () {
  this.callParent();
  yourInitComp()
}
Run Code Online (Sandbox Code Playgroud)

这些调用的最终顺序是:

- yourStuffBefore()
- base's ctor by callParent:
  - stuffBeforeIC()
  - initComponent:
    - base's initComponent by callParent
    - yourInitComp()
  - stuffAfterIC()
- yourStuffAfter()
Run Code Online (Sandbox Code Playgroud)

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