-->

How to cache subcomponents instance inside compone

2019-02-17 08:48发布

问题:

A lot of Sencha Touch examples I found online, don't really focus on proper view encapsulation. So the Controller listens to every single button's event even if the button is deep nested inside a view. In other words the internals of the view leak through which is never a good thing.

I found one good tutorial that encourages you to create meaningful views that listen to local events and raise meaningful business events etc.

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

However, one thing that I couldn't really figure out so far is how to best cache nested component instances. Consider this example:

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'
        }
    }
});

Let's say we want to add a method setSpecialUIState to our NotesListContainer. When it's called we want to do something with the newButton (e.g. hide it). How would I gain access to the newButton instance without misusing Ext.getComp() for that? Can I set it as an instance variable? How is the canonical way?

UPDATE

I just tried this as Nikolaj Borisik suggested.

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

That works like a charm. I just don't know if its idiomatic to do so or if there are any downsides I might be missing. Otherwise I would highly recommend to do this. Sencha aside, it's much better to compose meaningful views that abstract away coherent UI parts. That's much better than leak every single button through to the controller and fiddle directly with them.

So I wonder if there are any downsides of this approach?

回答1:

I see two options except using getComponent() :

1 Using Ext.create(...) for instance component

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 Move this logic into controller and use refs section

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'
                    }
                ]
            }
        ]

    }
});

I prefer the second option

I use both options, but in different case. I think that first option better suited for component that can be reused in other part of application or even in other project. But when we create some views that can be used on only once, i think not neccessary fired the custom events from view. We write more code, and duplicate it. Yes, 'newNoteCommand' more clear than 'tap', but { control : '#noteList [itemId='newButton'] give us all neccessary info. Second why i prefere second option, when we have a deep nesting of components. In this case we should fired event in first component, than fire event from his parent and so on, until controller has chance to handle it.



回答2:

I think the MVVM architecture aligns closer to your way of thinking. The 'VM' there is a controller like component that bridges the view with data/actions. KnockoutJS follows this paradigm and embeds data processing into your views.

Coming from the server side MVC pattern it was a big no-no to embed code into JSP. You only used tags that could do minimal logic to render data. And there is something to be said about doing this in a consistent way to greatly improve code maintainability.

However like you I found referring to the controller for all of the button handlers to be a bit overbearing. I think you could exercise judgement in figuring out where does the logic for my action go. If your action is reusable definitely use the DRY principal. If you are performing a simple view specific operation with limited logic- I might skip the controller.

To address the controller knowing too much about your view ... I agree that it cause refactoring issues. You could get around this by making your button handlers to emit custom events from their parent views. This way your controller just needs to listen for events from the view components it already knows about. The downside is you need a larger array of the custom events and document them really well, like sencha does with their events. Otherwise maintainability also suffers.



回答3:

After I spent more time with Sencha I figured out that I had a wrong assumption about Ext.getCmp(). I thought this would first query the DOM to find a matching ID and then would try to get the component instance which is bound to it. However, that's not what it does. In fact, it doesn't query the DOM at all. It just queries a facility called the ComponentManager which holds references to all components beeing used.

So, it's not that dirty to use it. However, we still can do better.

Every container supports the methods child(selector) and down(selector) to query for sub components. While at first glance this seems to query the DOM, again that does only query the ComponentManager. It uses the container at the starting point and queries down it's inner items. The difference between both is that child(selector) only queries the very first sub level whereas down(selector) queries all sub level.

I guess it's ok to use those to get a handle on those sub components. However, if there are still performance concerns because of repetitive calls to those method's I would recommend to cache those after the very first retrieval.