是否调用initComponent()方法?(涉及IT技术)

45

我在构建ExtJS 4中的应用程序时遇到了困难,其中一部分原因是不清楚何时在initComponent()中配置某些内容,何时不需要...

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

向下滚动时,当他们将存储器拆分为单独的类时,他们将其定义移到了initComponent()之外。有一个有用的评论引起了人们对此事的注意,但没有解释。(请参见创建模型和存储器部分)

我想理由应该很明显,但我还是没看出来。有什么指针吗?


1
在这里得到了一些非常好的答案,对于一个看起来相当无害的问题。干得好!我正在学习JavaScript和ExtJS,之前是从C#背景过来的。看到像这样试图深入了解事情的帖子真的很有用。我还发现那篇文章提出了许多问题,但并没有回答所有问题。虽然不能一次性解释所有东西。 - JonnyRaa
4个回答

44
如果您不深入了解ExtJS类系统的工作原理,可以按照以下方式操作:
initComponent()中声明所有非原始类型。
术语
- 原始类型 - 字符串、布尔值、整数等。 - 非原语类型 - 数组和对象。
解释
如果要创建超过一次的扩展组件,则在配置选项(在initComponent之外)中声明的任何非原始配置都将在所有实例之间共享。
因此,许多人在一个以上的选项卡上创建扩展组件(通常是扩展网格)时遇到问题。
这种行为在sra的答案和this Skirtle's Den article中有所解释。您可能还想阅读this SO question

4
你的回答没有明确表示这是你个人观点,与最佳实践和Sencha建议相悖。此外,我建议你重新阅读Skirtle的文章,因为在我看来,你完全误解了他的意思。实际上,除非有正当理由,他建议不要使用initComponent。 - Alex Tokarev
4
我不认为这是个人见解。将非原始类型定义为属性,所有实例都会共享它(除非你有意为之)。但缺少的是,声明不一定要在initComponent中严格执行,可以在任何方法中执行,然后通常由constructorinitComponent或其他方法调用。你能否提供一个反对这种做法的建议或最佳实践的链接? - sra
3
@AlexanderTokarev,在Stack Overflow上,每隔两周左右就会出现一个问题,其中包含无法正常工作/有错误的代码,解决方法是将数组/对象配置移到 initComponent() 中。对我来说很重要的是知道我是否提供了错误的信息。请问您能否更好地解释一下为什么您认为不应该给出这个建议? - Izhaki
对于这个好链接点个赞。看了其他一些材料后,我不确定是否同意该帖子,但我能理解你的观点。话虽如此,在大多数情况下,使用配置文件进行继承似乎是很重要的,因此这种方法在大多数地方都是可以接受的。 - JonnyRaa
啊,这个答案太令人困惑了,让我四处寻找其他博客或帖子。 - Horse Voice
显示剩余3条评论

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) config之间的区别,因为两者都是对象(实例)。但区别在于由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示例被简化了,解析配置可能并不总是那么容易。

结论

始终将类定义写成配置!除了纯粹的配置之外,它们不得包含任何被引用的实例,并且必须在创建实例时注意解决这些配置。

免责声明

我并不保证这篇短文涵盖了所有内容!


1
非常棒的文章!我认为我们都同意其中的对与错;唯一的问题是像 this.foo = Ext.apply({}, this.foo); 这样的代码在 ExtJS 库中并不明显或清晰。那么,如果没有一篇适当的 Sencha 文章,用户如何理解这些(相当复杂的主题)呢? - Izhaki
3
谢谢反馈,正如他们的开发团队成员经常说的那样:“不要害怕源代码”,我认为这个主题相当难以涵盖,而Sencha自4.x以来已经取得了巨大的进展,专注于新的MVC模式、性能和一些布局渲染问题。因此,他们的大部分主题都涵盖了这些内容。其他重要的部分,例如事件委托等等,我只是告诉我的团队中的每个人去查看源代码,所有的东西都在那里,API也是最好的之一。 - sra
@sra 不确定您有什么问题;我已经打开了那个JSFiddle,它看起来对我来说很好,网格渲染和插件都正常工作。您能具体说明一下吗? - Alex Tokarev
好帖子。在我看来,上面的示例可能有些过度设计,除非您想包含一些自定义属性进行配置。阅读一些前后的内容真的很有帮助-让我更好地理解了extjs中正在发生的事情。我之前没有意识到在类模板中声明的属性是静态的。 - JonnyRaa
1
这对于ExtJs 6仍然适用吗? - Flying Gambit
显示剩余9条评论

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 beta中的Ext.app.ControllerExt.app.Application
从更实际的角度来看,第二种方法更好,因为它更容易阅读和处理。一旦你掌握了这个想法,你会发现自己写所有的代码都像这样,因为这样做更容易。
这样看待它:如果你写一个旧式的Web应用程序,在服务器端生成HTML和其他东西,你会尝试不在代码中混杂任何HTML,对吧?模板在左边,代码在右边。这与在initComponent中硬编码数据几乎相同:当然可以工作,但只能到一定程度。然后它变成了一碗意大利面条,难以维护和扩展。哦,还有测试所有这些!恶心。
现在时候您需要在运行时对实例进行操作,而不是类定义时间 - 经典的示例是应用事件侦听器或在控制器中调用control。您将不得不从对象实例获取实际函数引用,并且您必须在initComponentinit中执行此操作。但是,我们正在努力解决这个问题 - 没有硬编码所有这些的硬要求;Observable.on()已经支持字符串侦听器名称,MVC之类的内容也很快就会支持。
正如我在上面的评论中所说,我将不得不为文档编写一篇文章或指南,解释事情。这可能要等到4.2发布才行;与此同时,这个答案应该能够对这个问题有所启示。

1
你完全忘了提到的是,例如网格会以特殊的方式处理这个已知的属性(store),并查看它是字符串、存储实例还是配置,然后覆盖该属性。这是针对特殊属性的已实现的特殊处理。 - sra
@sra 你混淆了类配置和运行时值。在上面的第二个示例中,store不是一个属性,而是一个配置选项 - 因为Ext.define处理的是而不是实例。当创建给定类的实例时,会定义属性 - 但不会更早。这样,store不是“特殊已知的属性”,它是一个配置选项,恰好与将在对象实例化时创建的实例属性具有相同的名称。 - Alex Tokarev
我觉得你误解了我的意思,所以我花了一些时间简要地写下了我的想法。请随意评论。(@Izhaki 我认为这也会引起您的兴趣) - sra
这个答案描述了我想要使用ExtJS 4的方式,但是我不得不与它作斗争。您介意根据这个建议看一下我最近提出的另一个问题吗?(https://dev59.com/W23Xa4cB1Zd3GeqPdlu3) - romacafe
@AlexTokarev,你似乎希望属性在实例创建之前不被定义,并且ext提供了工具来实现这一点(通常也是这样工作的),但实际上,在类模板中定义的内容成为实例中的静态属性/字段,除非它们被有意替换。 - JonnyRaa
@JonnyLeads 是的,差不多就是这样。在这方面,Ext JS 4 有点令人困惑;而Sencha Touch和Ext JS 5将会更加清晰易懂。 - Alex Tokarev

12

我在寻找答案时也遇到了同样的问题,看到这些答案让我感到失望。没有一个回答这个问题:initComponent()还是constructor?

很高兴知道类配置选项对象是共享的,您需要针对每个实例进行初始化/处理,但代码可以放在构造函数中,也可以放在initComponent()函数中。

我的猜测是,组件类的构造函数在中间某个位置调用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之间注入您的代码,这些内容可以在您将要扩展的类的构造函数中查找。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接