如何在组件内缓存子组件实例

Posted

技术标签:

【中文标题】如何在组件内缓存子组件实例【英文标题】:How to cache subcomponents instance inside components 【发布时间】:2012-09-17 14:48:58 【问题描述】:

我在网上找到了很多 Sencha Touch 示例,并没有真正关注正确的视图封装。因此,即使按钮深嵌套在视图中,控制器也会监听每个按钮的事件。换句话说,视图的内部泄漏从来都不是一件好事。

我找到了一个很好的教程,它鼓励您创建有意义的视图来监听本地事件并引发有意义的业务事件等。

http://miamicoder.com/2012/how-to-create-a-sencha-touch-2-app-part-2/

但是,到目前为止,我无法真正弄清楚的一件事是如何最好地缓存嵌套的组件实例。考虑这个例子:

Ext.define("NotesApp.view.NotesListContainer", 
    extend: "Ext.Container",
    alias: "widget.noteslistcontainer",

    initialize: function () 

        this.callParent(arguments);

        var newButton = 
            xtype: "button",
            text: 'New',
            ui: 'action',
            handler: this.onNewButtonTap,
            scope: this
        ;

        var topToolbar = 
            xtype: "toolbar",
            title: 'My Notes',
            docked: "top",
            items: [
                 xtype: 'spacer' ,
                newButton
            ]
        ;

        this.add([topToolbar]);
    ,
    onNewButtonTap: function () 
        console.log("newNoteCommand");
        this.fireEvent("newNoteCommand", this);
    ,
    config: 
        layout: 
            type: 'fit'
        
    
);

假设我们想在NotesListContainer 中添加一个方法setSpecialUIState。当它被调用时,我们想对newButton 做一些事情(例如隐藏它)。我如何在不滥用Ext.getComp() 的情况下访问newButton 实例?我可以将其设置为实例变量吗?规范的方式如何?

更新

我只是按照 Nikolaj Borisik 的建议尝试了这个。

    this._newButton = this.add([
            xtype: "button",
            text: 'New',
            ui: 'action',
            handler: this.onNewButtonTap,
            scope: this
        ];

这就像一个魅力。我只是不知道这样做是否符合习惯,或者是否有任何我可能遗漏的缺点。否则我强烈建议这样做。撇开 Sencha 不谈,编写有意义的视图来抽象出连贯的 UI 部分要好得多。这比将每个按钮都泄漏到控制器并直接摆弄它们要好得多。

所以我想知道这种方法是否有任何缺点?

【问题讨论】:

我完全支持你,但写控制器的方法是错误的...负责的事件总线覆盖所有组件的 fireEvent 方法,以便它们首先调用事件总线调度程序! 感谢您的澄清。我试图改进那部分。更好的?如果没有,请随意调整它;-) 你说“我完全支持你”。太好了,我们一起跳桥吧。不好玩,你不喜欢提交另一个答案吗?听听其他 Sencha/ExtJS 用户的观点会很好。我不可能是唯一一个喜欢用 Sencha/ExtJS 编写有意义的、连贯的视图组件的人。 【参考方案1】:

我看到除了使用 getComponent() 之外的两个选项:

1 使用 Ext.create(...) 作为实例组件

initialize: function ()         
   this.callParent(arguments);
   this.newButton = Ext.create('Ext.Button',
      xtype: "button",
      text: 'New',
      ui: 'action',
      handler: this.onNewButtonTap,
      scope: this
   );
   //....
,

setSpecialUIState : function()
   this.newButton.hide()

2 将此逻辑移入控制器并使用 refs 部分

Ext.define('NotesApp.controller.Home',
        extend : 'Ext.app.Controller',

        config : 
            refs :
                newButton : '#noteList [itemId=newButton]'
            ,

            control :
                newButton : 
                    tap : 'onNewButtonTap'
                
            

        ,

        onNewButtonTap : function()
            console.log('on new Button tap');
        ,

        setSpecialUIState : function()
           this.getNewButton.hide()
        

    );



Ext.define("NotesApp.view.NotesListContainer", 
    extend :"Ext.Container",
    alias  :"widget.noteslistcontainer",
    id     :'noteList',
    config:
        items:[
            
                xtype  :"toolbar",
                title  :'My Notes',
                docked :"top",
                items:[
                     xtype:'spacer' ,
                    
                        xtype   :"button",
                        text    :'New',
                        ui      :'action',
                        itemId  :'newButton'
                    
                ]
            
        ]

    
);

我更喜欢第二种方式

我使用这两个选项,但在不同的情况下。我认为第一个选项更适合可以在应用程序的其他部分甚至其他项目中重用的组件。但是当我们创建一些只能使用一次的视图时,我认为没有必要从视图中触发自定义事件。我们编写更多代码,并复制它。是的,'newNoteCommand' 比 'tap' 更清晰,但是 control : '#noteList [itemId='newButton'] 给了我们所有必要的信息。第二为什么我更喜欢第二种选择,当我们有深层嵌套的组件时。在这种情况下,我们应该在第一个组件中触发事件,而不是从他的父级触发事件等等,直到控制器有机会处理它。

【讨论】:

是的,我刚刚尝试了第一个选项并且它有效。我只是不知道这样做是否符合习惯,或者是否有任何我可能遗漏的缺点。否则我会强烈推荐第一个版本。撇开 Sencha 不谈,编写有意义的视图来抽象出连贯的 UI 部分要好得多。这比将每个按钮都泄漏到控制器并直接摆弄它们要好得多。 我认为第一个选项更适合可以在应用程序的其他部分甚至其他项目中重用的组件。但是当我们创建一些只能使用一次的视图时,我认为没有必要从视图中触发自定义事件。我们编写更多代码,并复制它。 'newNoteCommand' 比 'tap' 更清晰,但 control : '#noteList [itemId='newButton'] 为我们提供了所有必要的信息。第二个为什么我更喜欢第二种选择,当我们有深层嵌套的组件时。在这种情况下,我们应该在第一个组件中触发事件,而不是从父组件触发事件等等,直到控制器处理它。 嗯,是的。重用的组件更重要。但是,我倾向于为此处理我的所有代码!当你写 "but control : '#noteList [itemId='newButton'] give us all neccessary info" 我读到:控制器有太多关于视图的信息.如果它对内部工作了解太多,那么重构东西就会变成一场噩梦。【参考方案2】:

我认为 MVVM 架构更符合您的思维方式。 'VM' 有一个类似控制器的组件,它将视图与数据/操作连接起来。 KnockoutJS 遵循这种范式并将数据处理嵌入到您的视图中。

来自服务器端 MVC 模式,将代码嵌入 JSP 是一个很大的禁忌。您只使用了可以执行最少逻辑来呈现数据的标签。关于以一致的方式执行此操作以大大提高代码可维护性,有一些话要说。

但是,我发现与您一样,为所有按钮处理程序引用控制器有点霸道。我认为你可以判断我的行动逻辑在哪里。如果您的操作可重用,请务必使用 DRY 主体。如果您正在执行具有有限逻辑的简单视图特定操作 - 我可能会跳过控制器。

解决控制器对您的视图了解太多...我同意它会导致重构问题。您可以通过使您的按钮处理程序从其父视图发出自定义事件来解决此问题。这样你的控制器只需要监听它已经知道的视图组件的事件。缺点是您需要更大的自定义事件数组并很好地记录它们,就像 sencha 对它们的事件所做的那样。否则可维护性也会受到影响。

【讨论】:

是的,我非常同意所有这些。是的,我知道 MVVM 并在过去使用过 knockoutJS 和 AngularJS :)【参考方案3】:

在我花更多时间在 Sencha 上之后,我发现我对 Ext.getCmp() 的假设是错误的。我认为这将首先查询 DOM 以找到匹配的 ID,然后尝试获取绑定到它的组件实例。但是,这不是它的作用。事实上,它根本不查询 DOM。它只是查询一个名为 ComponentManager 的工具,该工具包含对所有正在使用的组件的引用。

所以,使用它并不那么脏。但是,我们仍然可以做得更好。

每个容器都支持child(selector)down(selector) 方法来查询子组件。乍一看,这似乎是在查询 DOM,但实际上它只查询了 ComponentManager。它在起点使用容器并向下查询它的内部项目。两者的区别在于child(selector) 只查询第一个子级别,而down(selector) 查询所有子级别。

我想使用它们来处理这些子组件是可以的。但是,如果由于重复调用这些方法而仍然存在性能问题,我建议在第一次检索后缓存它们。

【讨论】:

以上是关于如何在组件内缓存子组件实例的主要内容,如果未能解决你的问题,请参考以下文章

如何为组件的每个实例触发 vue 生命周期事件?

如何在 Angular 中显示具有自己路由的父组件内的特定子组件?

Vue父组件如何调用子组件(弹出框)中的方法的问题

如何在子列表的插槽内分配组件

如何销毁父组件内的子组件

vue中动态路由组件缓存及生命周期函数