web-dev-qa-db-fra.com

à initComponent () ou non à initComponent ()

Je rencontre des difficultés lors de la construction d'une application dans ExtJS 4, et une partie de cela est une confusion sur le moment de configurer quelque chose dans initComponent () et quand ne pas ...

Par exemple, dans le propre Sencha MVC Application Architecture doc, lors de la première création de la vue grille, ils ont défini le magasin en ligne dans la méthode initComponent (). (Voir la section "Définition d'une vue")

Plus bas, lorsqu'ils ont factorisé le magasin dans une classe distincte, ils ont déplacé la définition en dehors de initComponent (). Il y a un commentaire utile qui attire l'attention sur ce fait, mais il n'y a aucune explication. (Voir la section Création d'un modèle et d'un magasin)

Je suppose que la raison est censée être évidente, mais je la manque. Des pointeurs?

44
romacafe

Si vous n'avez pas une compréhension approfondie du fonctionnement du système de classe ExtJS, vous pouvez suivre ceci:

Déclarez tous les types non primitifs dans initComponent().

Terminologie

  • Types primitifs - chaînes, booléens, entiers, etc.
  • Non primitives - tableaux et objets.

Explication

Si le composant que vous étendez doit être créé plusieurs fois, toutes les configurations non primitives déclarées comme option de configuration (en dehors de initComponent) seront partagées entre toutes les instances.

Pour cette raison, de nombreuses personnes ont rencontré des problèmes lorsqu'un composant étendu (généralement une grille étendue) est créé sur plusieurs onglets.

Ce comportement est expliqué dans la réponse de sra ci-dessous et dans cet article de Skirtle's Den . Vous pouvez également lire cette SO question .

44
Izhaki

Je vais d'abord prendre position sur mes commentaires:

@AlexanderTokarev Ne vous méprenez pas. Je ne parle pas de configurations de composants, ou bien pire d'instances et de les déplacer vers initComponent(), ce n'est pas mon point.

Maintenant, ce que j'en pense.

initComponent () devrait résoudre tout ce qui est nécessaire au moment où une instance de cette classe est créée. Ni plus ni moins.

Vous pouvez gâcher une charge lors de la définition des classes et la plupart du temps cela se produit car les gens ne comprennent pas comment fonctionne le système de classes ExtJS. Comme il s'agit de composants, ce qui suit se concentrera sur ceux-ci. Ce sera également un exemple simplifié qui ne devrait montrer qu'une sorte d'erreur que j'ai vue de nombreuses fois.

Commençons: nous avons un panneau personnalisé qui fait beaucoup de choses astucieuses. Cela soulève le besoin d'une configuration personnalisée, nous l'appelons foo. Nous l'ajoutons avec notre option de configuration par défaut à la définition de classe afin que nous puissions y accéder:

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

    foo: {
        bar: null  
    },

    initComponent: function() {
        this.callParent(arguments);
    }
});

Mais les choses deviennent bizarres après les tests. Les valeurs de nos configurations semblent changer comme par magie. Voici un JSFiddle Ce qui s'est passé, c'est que toutes les instances créées font référence à la même instance foo. Mais récemment, je l'ai fait

store: {
    fields: [ ... ],
    proxy: {
        type: 'direct',
        directFn: 'Direct.Store.getData'
    }
}

avec un magasin et cela a fonctionné. Alors, pourquoi foo ne fonctionne-t-il pas?

La plupart des gens ne voient aucune différence entre ce petit objet foo et une configuration (ExtJS) qui est fondamentalement correcte car les deux sont des objets (instances). Mais la différence est que toutes les classes livrées par sencha savent parfaitement quelles propriétés de configuration elles attendent et s'en occupent.

Par exemple, la propriété store d'une grille est résolue par le StoreManager et peut donc être:

  • storeId chaîne, ou
  • instance de magasin ou
  • stocker l'objet de configuration.

Lors de l'initialisation de la grille, ces deux problèmes sont résolus et remplacés par une instance de magasin réelle. Un magasin n'est qu'un exemple. Je suppose que le plus connu est le tableau des éléments. Il s'agit d'un tableau au moment de la définition et il est remplacé pour chaque instance avec un MixedCollection (si je ne me trompe pas).

Oui, il existe une différence entre une définition de classe et l'instance créée à partir de celle-ci. Mais nous devons prendre soin de toute nouvelle propriété qui contient une référence comme le foo d'en haut et ce n'est pas si compliqué. Voici ce que nous devons faire pour le corriger pour l'exemple 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);
    }
});

Voici le JSFiddle

Maintenant, nous prenons soin de la configuration foo lorsqu'une instance est créée. Cet exemple foo est maintenant simplifié et il ne sera pas toujours aussi facile de résoudre une configuration.

Conclusion

Écrivez toujours votre définition de classe en tant que configurations! Ils ne doivent contenir aucune instance référencée, à l'exception d'une configuration simple, et doivent en prendre soin pour les résoudre lorsqu'une instance est créée.

Clause de non-responsabilité

Je ne prétends pas tout couvrir avec cette écriture vraiment courte !

22
sra

Je préconise généralement d'avoir autant de configuration que possible dans les options de configuration de classe, car elle se lit mieux et est plus facile à remplacer dans les sous-classes. En plus de cela, il y a une forte possibilité qu'à l'avenir Sencha Cmd aura un compilateur d'optimisation, donc si vous gardez votre code déclaratif, il pourrait bénéficier d'optimisations.

Comparer:

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();

Et:

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

Notez comment ces approches sont très différentes. Dans le premier exemple, nous codons en dur beaucoup: valeurs de propriété d'objet, configuration de magasin, nom de classe MyPanel lorsqu'il est utilisé; nous tuons pratiquement l'idée d'une classe parce qu'elle devient inextensible. Dans le deuxième exemple, nous créons un modèle qui peut être réutilisé plusieurs fois avec une configuration éventuellement différente - en gros, c'est de cela qu'il s'agit pour tout le système de classe .

Cependant, la différence réelle est plus profonde. Dans le premier cas, nous reportons effectivement la configuration de la classe jusqu'à l'exécution, tandis que dans le second cas, nous définissons la configuration de la classe et en l'appliquant à des phases très distinctes. En fait, nous pouvons facilement dire que la deuxième approche introduit quelque chose qui manque à JavaScript nativement: la phase de compilation. Et cela nous donne une pléthore de possibilités qui sont exploitées dans le code cadre lui-même; si vous voulez des exemples, jetez un œil à Ext.app.Controller et Ext.app.Application dans la dernière version bêta 4.2.

D'un point de vue plus pratique, la deuxième approche est meilleure car elle est plus facile à lire et à gérer. Une fois que vous aurez compris l'idée, vous vous retrouverez à écrire tout votre code comme ça, car c'est juste plus facile de cette façon.

Regardez-le de cette façon: si vous écriviez une application Web à l'ancienne, générant du HTML et des trucs côté serveur, vous essaieriez de ne pas mélanger du HTML avec le code, n'est-ce pas? Modèles à gauche, code à droite. C'est pratiquement la même chose que les données codées en dur dans initComponent: bien sûr, cela fonctionne, jusqu'à un certain point. Ensuite, il devient un bol de spaghetti, difficile à entretenir et à étendre. Oh, et tester tout ça! Beurk.

Maintenant, il y a sont fois où vous devez faire quelque chose avec une instance au moment de l'exécution, par opposition à une heure de définition de classe - l'exemple classique applique des écouteurs d'événements ou appelle control dans les contrôleurs. Vous devrez prendre les références de fonction réelles de l'instance d'objet, et vous devez le faire dans initComponent ou init. Cependant, nous travaillons à atténuer ce problème - il ne devrait pas y avoir d'obligation stricte de coder en dur tout cela; Observable.on() prend déjà en charge les noms d'écouteurs de chaînes et les trucs MVC le seront aussi sous peu.

Comme je l'ai dit dans les commentaires ci-dessus, je vais devoir écrire un article ou un guide pour les docs, expliquant les choses. Cela devrait probablement attendre la sortie de la version 4.2; en attendant, cette réponse devrait éclairer la question, espérons-le.

14
Alex Tokarev

J'ai cherché une réponse à la même question lorsque je me suis retrouvé ici et voir ces réponses m'a déçu. Aucun de ceux-ci ne répond à la question: initComponent () ou constructeur?

Il est agréable de savoir que les objets d'options de configuration de classe sont partagés et que vous devez les initialiser/les traiter par instance, mais le code peut aller dans le constructeur ainsi que dans la fonction initComponent ().

Ma supposition était que le constructeur de la classe Component appelle initComponent () quelque part au milieu et je n'avais pas très tort: ​​je n'ai eu qu'à regarder le code source, c'est en fait le constructeur de AbstractComponent .

Il ressemble donc à ceci:

AbstractComponent/ctor:
- stuffBeforeIC()
- initComponent()
- stuffAfterIC()

Maintenant, si vous étendez un composant, vous obtiendrez quelque chose comme ceci:

constructor: function () {
  yourStuffBefore();
  this.callParent(arguments);
  yourStuffAfter();
},
initComponent: function () {
  this.callParent();
  yourInitComp()
}

L'ordre final de ces appels est le suivant:

- yourStuffBefore()
- base's ctor by callParent:
  - stuffBeforeIC()
  - initComponent:
    - base's initComponent by callParent
    - yourInitComp()
  - stuffAfterIC()
- yourStuffAfter()

Donc, au final, tout dépend si vous voulez/devez injecter votre code entre stuffBeforeIC et stuffAfterIC que vous pouvez rechercher dans le constructeur de la classe que vous allez étendre.

12