【问题标题】:to initComponent() or not to initComponent()到 initComponent() 或不到 initComponent()
【发布时间】:2013-01-07 16:02:01
【问题描述】:

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

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

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

我想原因应该很明显,但我错过了。有什么指点吗?

【问题讨论】:

  • 在这里对一个看起来相当无害的问题得到了一些很好的答案。干得好!我刚从 c# 背景学习 javascript 和 extjs。看到试图深入了解此类事情的帖子真的很有用。我还发现这篇文章提出的问题与它所回答的问题一样多。假设您不能一口气解释所有内容

标签: extjs extjs4 extjs4.1


【解决方案1】:

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

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

术语

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

说明

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

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

以下 sra 的回答和 this Skirtle's Den article 中解释了此行为。您可能还想阅读this SO question

【讨论】:

  • 您的回答未能明确表明您的个人愿景违背了最佳实践和 Sencha 建议。另外,我建议您重新访问 Skirtle 的文章,因为在我看来您完全错了。他实际上是在推荐反对使用 initComponent 除非有正当理由。
  • @AlexanderTokarev 我看不出有任何理由说明这是个人的愿景。将 Non-Primitive 类型定义为属性,所有实例都将共享它(除非您对它做某事)但缺少的是,声明不需要严格地在initComponent 它可以通过任何通常由constructorinitComponent 甚至任何其他方法调用的方法来完成。您能否将我链接到与此相反的建议或最佳实践?
  • @AlexanderTokarev,在这里,我们每两周左右收到一个关于非工作/错误代码的问题,解决方案是将数组/对象配置移动到 initComponent()。知道我是否提供了错误信息对我来说很重要。您能否更好地解释为什么您认为不应给出此建议?
  • @Izhaki 既然你提到了它,我意识到我们没有针对这个问题的指南;确实,应用架构指南提倡错误的方法。我将花时间写一篇文章来描述差异以及为什么声明式方法更好。不过,这可能需要一些时间。感谢您引起我的注意!
  • @Izhaki 从广义上讲,问题在于人们不了解 Ext 类系统是什么,并试图与之抗争而不是与之抗衡。将损坏的代码移至initComponent 给人一种解决问题的印象,但实际上它只是将其转移到其他地方。根据经验,您应该避免手动创建组件 - 使用子类来应用配置选项和存储组件类所需的逻辑,然后通过 xtype 再次将它们重用为配置选项。我会在下面的回答中尝试更多地解释它。
【解决方案2】:

首先我要对我的评论表明立场:

@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 提供的类都非常清楚他们期望和处理的配置属性。

例如,网格的 store 属性由 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 示例已经简化,解析配置并不总是那么容易。

结论

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

免责声明

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

【讨论】:

  • 现在更清楚了,谢谢。基本上你做对了,唯一缺少的就是术语。在 Ext JS 类系统用语中,术语“属性”是指实例属性静态类属性。它是一个运行时的东西only。 OTOH“配置选项”虽然在技术上是类定义对象的属性,但在语义上具有非常不同的含义。它应该被视为不可变的“编译”时间语句。 (在下一条评论中继续)
  • 当然,从技术上讲,它不是一个声明,它是可变的,但想法是这样的:将您的类声明为不可变模板,将配置选项定义为其中的一部分,以及当该类的对象被实例化时,所有配置选项默认成为属性 - 除非你对它们做一些特殊的事情,就像上面一样。所以这种方式,store 是出了名的错误示例:理论上,它应该只接受字符串 storeId 或存储配置对象,因为它们是不可变的。但它也接受商店实例,用于歇斯底里的葡萄干,这就是让人们感到困惑的地方。
  • 优秀的帖子!我想我们都同意是非;唯一的问题是 this.foo = Ext.apply({}, this.foo); 这样的行在 ExtJS 库中不可见或不清晰。那么用户如何在没有适当的 Sencha 文章的情况下掌握这些(相当复杂的主题)?
  • @Izhaki 感谢您的反馈。正如他们的大多数开发团队成员经常说的那样:不要害怕源代码我认为这个话题很难涵盖,而且 sencha 自 4.x 以来已经取得了巨大的进步,重点是新的 MVC 模式、性能和一些布局渲染错误。所以他们的大部分主题都涵盖了这部分。以及其他重要部分,如事件委托等。我告诉团队中的每个人都查看源代码,它就在那里,API 是最好的之一。
  • 这仍然适用于 ExtJs 6 吗?
【解决方案3】:

我通常主张在类配置选项中尽可能多地进行配置,因为它读起来更好并且更容易在子类中覆盖。除此之外,很有可能在未来 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 也将支持。

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

【讨论】:

  • 您完全没有提及的是 f.e.网格以特殊方式处理这个已知属性(存储),并查看它是字符串、存储实例还是配置,然后覆盖该属性。这是仅针对特殊属性实施的特殊处理。
  • @sra 您混淆了类配置和运行时值。在上面的第二个示例中,store 不是一个属性,而是一个配置选项 - 因为Ext.define 处理的是 classes 而不是 instances。创建给定类的实例时,会定义属性 - 但不会更早。这样,store 不是“特殊的已知属性”,它是一个配置选项,恰好与将在对象实例化时创建的实例属性同名。
  • 我认为你误解了我的意思,所以我花了一些时间简单地写下我对此的看法。请随意发表评论。 (@Izhaki 我想你也会对此感兴趣)
  • 这个答案描述了我想要使用 ExtJS 4 的方式,但我不得不与之抗争。你介意看看我最近根据这个建议提出的另一个问题吗? (stackoverflow.com/questions/14391404/…)
  • @AlexTokarev 似乎您希望在创建实例之前不定义属性,并且 ext 提供了使其工作的工具(并且通常以这种方式工作)但是在类模板中定义的实际事物成为实例中的静态属性/字段,除非它们被故意替换
【解决方案4】:

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

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

我的猜测是 Component 类的构造函数在中间某处调用了 initComponent(),我并没有错:只需要查看源代码,它实际上是 AbstractComponent's constructor

所以它看起来像这样:

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 之间注入你的代码,你可以在你要扩展的类的构造函数中查找。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-10-02
    • 1970-01-01
    • 2011-03-11
    • 2017-03-25
    • 2013-05-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多