Come gestire l'inizializzazione e il rendering delle visualizzazioni secondarie in Backbone.js?


199

Ho tre modi diversi per inizializzare e renderizzare una vista e le sue viste secondarie, e ognuna di esse presenta problemi diversi. Sono curioso di sapere se esiste un modo migliore per risolvere tutti i problemi:


Scenario 1:

Inizializza i bambini nella funzione di inizializzazione del genitore. In questo modo, non tutto viene bloccato nel rendering in modo da ridurre il blocco del rendering.

initialize : function () {

    //parent init stuff

    this.child = new Child();
},

render : function () {

    this.$el.html(this.template());

    this.child.render().appendTo(this.$('.container-placeholder');
}

I problemi:

  • Il problema maggiore è che la chiamata al rendering del genitore per la seconda volta rimuoverà tutti i binding di eventi del figlio. (Questo è dovuto al modo in cui funziona jQuery $.html().) Questo potrebbe essere mitigato chiamando this.child.delegateEvents().render().appendTo(this.$el);invece, ma poi il primo, e il caso più spesso, stai facendo più lavoro inutilmente.

  • Aggiungendo i bambini, imponi alla funzione di rendering di conoscere la struttura DOM dei genitori in modo da ottenere l'ordine che desideri. Ciò significa che la modifica di un modello potrebbe richiedere l'aggiornamento della funzione di rendering di una vista.


Scenario due:

Inizializza i figli nell'immagine del genitore initialize(), ma invece di aggiungere, usa setElement().delegateEvents()per impostare il figlio su un elemento nel modello genitori.

initialize : function () {

    //parent init stuff

    this.child = new Child();
},

render : function () {

    this.$el.html(this.template());

    this.child.setElement(this.$('.placeholder-element')).delegateEvents().render();
}

I problemi:

  • Questo rende il delegateEvents()necessario ora, il che è leggermente negativo in quanto è necessario solo per le chiamate successive nel primo scenario.

Scenario tre:

Inizializza invece i figli nel render()metodo del genitore .

initialize : function () {

    //parent init stuff
},

render : function () {

    this.$el.html(this.template());

    this.child = new Child();

    this.child.appendTo($.('.container-placeholder').render();
}

I problemi:

  • Ciò significa che ora la funzione di rendering deve essere collegata anche a tutta la logica di inizializzazione.

  • Se modifico lo stato di una delle viste figlio e quindi chiamo rendering sul genitore, verrà creato un figlio completamente nuovo e tutto il suo stato attuale andrà perso. Il che sembra anche che potrebbe diventare rischioso per le perdite di memoria.


Davvero curioso di convincere i tuoi ragazzi a farlo. Quale scenario useresti? o ce n'è un quarto magico che risolve tutti questi problemi?

Hai mai tenuto traccia di uno stato renderizzato per una vista? Dì una renderedBeforebandiera? Sembra davvero stravagante.


1
di solito non sto registrando riferimenti a viste figlio su viste padre poiché la maggior parte delle comunicazioni avviene attraverso modelli / raccolte ed eventi innescati da modifiche a queste. Sebbene il terzo caso sia il più vicino a quello che sto usando la maggior parte del tempo. Caso 1 dove ha senso. Inoltre, la maggior parte delle volte non dovresti ripetere il rendering dell'intera vista, ma piuttosto della parte che è cambiata
Tom Tu,

Certo, renderò sicuramente solo le parti modificate quando possibile, ma anche allora penso che la funzione di rendering dovrebbe essere disponibile e comunque non distruttiva. Come gestisci non avere un riferimento ai figli nel genitore?
Ian Storm Taylor,

Di solito ascolto eventi sui modelli associati alle viste figlio - se voglio fare qualcosa di più personalizzato, associo gli eventi alle viste secondarie. Se è necessario questo tipo di "comunicazione", di solito creo un metodo di supporto per la creazione di sottoview che lega gli eventi, ecc.
Tom Tu,

1
Per una discussione correlata di livello superiore, consultare: stackoverflow.com/questions/10077185/… .
Ben Roberts,

2
Nello scenario due, perché è necessario chiamare delegateEvents()dopo il setElement()? Come da documenti: "... e sposta gli eventi delegati della vista dal vecchio elemento al nuovo", il setElementmetodo stesso dovrebbe gestire la delega degli eventi.
rdamborsky,

Risposte:


260

Questa è un'ottima domanda La spina dorsale è eccezionale a causa della mancanza di ipotesi che fa, ma significa che devi (decidere come) implementare cose come questa da solo. Dopo aver esaminato le mie stesse cose, trovo che (tipo di) uso un mix di scenario 1 e scenario 2. Non penso che esista un quarto scenario magico perché, semplicemente, tutto ciò che fai negli scenari 1 e 2 deve essere fatto.

Penso che sarebbe più semplice spiegare come mi piace gestirlo con un esempio. Supponiamo che questa semplice pagina sia suddivisa nelle viste specificate:

Ripartizione della pagina

Supponiamo che l'HTML sia, dopo essere stato reso, qualcosa del genere:

<div id="parent">
    <div id="name">Person: Kevin Peel</div>
    <div id="info">
        First name: <span class="first_name">Kevin</span><br />
        Last name: <span class="last_name">Peel</span><br />
    </div>
    <div>Phone Numbers:</div>
    <div id="phone_numbers">
        <div>#1: 123-456-7890</div>
        <div>#2: 456-789-0123</div>
    </div>
</div>

Spero che sia abbastanza ovvio come l'HTML corrisponda al diagramma.

Il ParentViewstive 2 Visto bambino, InfoViewe PhoneListViewcosì come un paio di div in più, uno dei quali, #name, deve essere impostata a un certo punto. PhoneListViewcontiene viste secondarie proprie, una matrice di PhoneViewvoci.

Quindi passiamo alla tua vera domanda. Gestisco l'inizializzazione e il rendering in modo diverso in base al tipo di vista. Suddivido le viste in due tipi, Parentviste e Childviste.

La differenza tra loro è semplice, le Parentviste mantengono le viste figlio mentre le Childviste no. Quindi nel mio esempio, ParentViewe PhoneListViewsono Parentviste, mentre InfoViewe le PhoneViewvoci sono Childviste.

Come ho detto prima, la differenza più grande tra queste due categorie è quando è consentito il rendering. In un mondo perfetto, voglio che le Parentviste vengano visualizzate solo una volta. Spetta alle loro viste figlio gestire qualsiasi nuovo rendering quando i modelli cambiano. Childle viste, d'altra parte, concedo di eseguire nuovamente il rendering in qualsiasi momento necessario poiché non hanno altre viste che fanno affidamento su di esse.

Più in dettaglio, per le Parentviste mi piace che le mie initializefunzioni facciano alcune cose:

  1. Inizializza il mio punto di vista
  2. Rendi il mio punto di vista
  3. Crea e inizializza le viste figlio.
  4. Assegna a ciascun bambino una vista a mio avviso (ad es., InfoViewSarebbe assegnata #info).

Il passaggio 1 è piuttosto autoesplicativo.

Il passaggio 2, il rendering, viene eseguito in modo tale che tutti gli elementi su cui si basano le viste figlio esistano già prima che provi ad assegnarli. In questo modo, so che tutti i bambini eventssaranno impostati correttamente e posso ri-renderizzare i loro blocchi tutte le volte che voglio senza preoccuparmi di dover delegare nuovamente qualsiasi cosa. In realtà non ho renderalcuna visione di bambino qui, lascio che lo facciano da soli initialization.

I passaggi 3 e 4 vengono effettivamente gestiti contemporaneamente al passaggio eldurante la creazione della vista figlio. Mi piace inserire un elemento qui perché sento che il genitore dovrebbe determinare dove a suo avviso il bambino può mettere il suo contenuto.

Per il rendering, provo a renderlo piuttosto semplice per le Parentviste. Voglio che la renderfunzione non faccia altro che rendere la vista padre. Nessuna delegazione di eventi, nessun rendering di viste figlio, niente. Solo un semplice rendering.

A volte questo non funziona sempre però. Ad esempio, nel mio esempio sopra, l' #nameelemento dovrà essere aggiornato ogni volta che cambia il nome all'interno del modello. Tuttavia, questo blocco fa parte del ParentViewmodello e non è gestito da una Childvista dedicata , quindi ci aggiro. Creerò una sorta di subRenderfunzione che sostituisce solo il contenuto #namedell'elemento e non è necessario eliminare l'intero #parentelemento. Questo può sembrare un trucco, ma ho davvero trovato che funziona meglio che doverti preoccupare di ripetere il rendering dell'intero DOM e ricollegare elementi e così via. Se volessi davvero renderlo pulito, creerei una nuova Childvista (simile alla InfoView) che gestirà il #nameblocco.

Ora per le Childviste, initializationè abbastanza simile alle Parentviste, solo senza la creazione di ulteriori Childviste. Così:

  1. Inizializza la mia vista
  2. Il programma di installazione vincola l'ascolto di eventuali modifiche al modello a cui tengo
  3. Rendi il mio punto di vista

Childvisualizzare il rendering è anche molto semplice, basta renderizzare e impostare il contenuto di my el. Ancora una volta, non si scherza con la delegazione o qualcosa del genere.

Ecco un esempio di codice di come ParentViewpotrebbe apparire il mio :

var ParentView = Backbone.View.extend({
    el: "#parent",
    initialize: function() {
        // Step 1, (init) I want to know anytime the name changes
        this.model.bind("change:first_name", this.subRender, this);
        this.model.bind("change:last_name", this.subRender, this);

        // Step 2, render my own view
        this.render();

        // Step 3/4, create the children and assign elements
        this.infoView = new InfoView({el: "#info", model: this.model});
        this.phoneListView = new PhoneListView({el: "#phone_numbers", model: this.model});
    },
    render: function() {
        // Render my template
        this.$el.html(this.template());

        // Render the name
        this.subRender();
    },
    subRender: function() {
        // Set our name block and only our name block
        $("#name").html("Person: " + this.model.first_name + " " + this.model.last_name);
    }
});

Puoi vedere la mia implementazione di subRenderqui. Avendo le modifiche vincolate subRenderinvece di render, non devo preoccuparmi di far saltare in aria e ricostruire l'intero blocco.

Ecco un esempio di codice per il InfoViewblocco:

var InfoView = Backbone.View.extend({
    initialize: function() {
        // I want to re-render on changes
        this.model.bind("change", this.render, this);

        // Render
        this.render();
    },
    render: function() {
        // Just render my template
        this.$el.html(this.template());
    }
});

I vincoli sono la parte importante qui. Legando al mio modello, non devo mai preoccuparmi di chiamarmi manualmente render. Se il modello cambia, questo blocco si riattiverà da solo senza influire su altre viste.

Il PhoneListViewsarà simile a quello ParentView, avrete solo bisogno di un po 'più logica in entrambe le vostre initializatione renderle funzioni alle collezioni manico. Il modo in cui gestisci la raccolta dipende davvero da te, ma dovrai almeno ascoltare gli eventi della raccolta e decidere come renderizzare (aggiungi / rimuovi, o semplicemente esegui nuovamente il rendering dell'intero blocco). Personalmente mi piace aggiungere nuove viste e rimuovere quelle vecchie, non ricalcolare l'intera vista.

Il PhoneViewsarà quasi identico al InfoViewsolo ascoltando il modello cambia si preoccupa.

Spero che questo abbia aiutato un po ', per favore fatemi sapere se qualcosa è confuso o non abbastanza dettagliato.


8
Spiegazione davvero approfondita grazie. Domanda però, ho sentito e tendo a concordare sul fatto che chiamare renderall'interno del initializemetodo sia una cattiva pratica, perché ti impedisce di essere più performante nei casi in cui non vuoi renderizzare immediatamente. Cosa ne pensi di questo?
Ian Storm Taylor,

Sicuro. Hmm ... Cerco solo di inizializzare le viste che dovrebbero essere mostrate in questo momento (o potrebbero essere mostrate in un prossimo futuro, ad esempio un modulo di modifica che ora è nascosto ma mostrato facendo clic su un collegamento di modifica), quindi dovrebbero essere visualizzate immediatamente. In caso affermativo, ho un caso in cui una vista impiega un po 'di tempo per il rendering, personalmente non creo la vista stessa fino a quando non deve essere mostrata, quindi nessuna inizializzazione e rendering avranno luogo fino a quando necessario. Se hai un esempio, posso provare ad approfondire il modo in cui lo gestisco ...
Kevin Peel,

5
È una grande limitazione non poter rieseguire il rendering di ParentView. Ho una situazione in cui sostanzialmente ho un controllo a schede, in cui ogni scheda mostra un ParentView diverso. Vorrei semplicemente ri-renderizzare il ParentView esistente quando si fa clic una seconda volta su una scheda, ma farlo causa la perdita di eventi nelle ChildView (creo i bambini in init e li render in render). Suppongo che 1) nascondi / mostri invece di render, 2) delegateEvents nel rendering di ChildViews, oppure 3) crei figli in render o 4) non preoccuparti di perf. prima che sia un problema e ricreare ogni volta ParentView. Hmmmm ....
Paul Hoenecke,

Devo dire che è una limitazione nel mio caso. Questa soluzione funzionerà bene se non provi a eseguire nuovamente il rendering del genitore :) Non ho una buona risposta ma ho la stessa domanda di OP. Spero ancora di trovare il modo corretto di "Backbone". A questo punto, sto pensando di nascondere / mostrare e non eseguire nuovamente il rendering è la strada da percorrere per me.
Paul Hoenecke,

1
come passi la collezione nel bambino PhoneListView?
Tri Nguyen,


6

A me non sembra la peggior idea al mondo di distinguere tra la configurazione iniziale e le successive configurazioni delle tue viste tramite una sorta di bandiera. Per rendere tutto più semplice e pulito, il flag dovrebbe essere aggiunto alla tua vista personale che dovrebbe estendere la vista dorsale (base).

Come nel caso di Derick, non sono del tutto sicuro che questo risponda direttamente alla tua domanda, ma penso che valga la pena menzionarlo in questo contesto.

Vedi anche: Uso di un Eventbus in Backbone


3

Kevin Peel dà un'ottima risposta - ecco la mia versione tl; dr:

initialize : function () {

    //parent init stuff

    this.render(); //ANSWER: RENDER THE PARENT BEFORE INITIALIZING THE CHILD!!

    this.child = new Child();
},

2

Sto cercando di evitare l'accoppiamento tra viste come queste. Di solito ci sono due modi:

Usa un router

Fondamentalmente, si consente al router di inizializzare la vista padre e figlio. Quindi la vista non si conosce l'una con l'altra, ma il router gestisce tutto.

Passando lo stesso el ad entrambe le viste

this.parent = new Parent({el: $('.container-placeholder')});
this.child = new Child({el: $('.container-placeholder')});

Entrambi hanno la conoscenza dello stesso DOM e puoi ordinarli come preferisci.


2

Quello che faccio è dare a ogni bambino un'identità (che Backbone ha già fatto per te: cid)

Quando Container esegue il rendering, utilizzando 'cid' e 'tagName' genera un segnaposto per ogni bambino, quindi nel modello i bambini non hanno idea di dove verrà inserito dal Contenitore.

<tagName id='cid'></tagName>

di quanto tu possa usare

Container.render()
Child.render();
this.$('#'+cid).replaceWith(child.$el);
// the rapalceWith in jquery will detach the element 
// from the dom first, so we need re-delegateEvents here
child.delegateEvents();

non è necessario alcun segnaposto specificato e Container genera solo il segnaposto anziché la struttura DOM per bambini. Cotainer e Children stanno ancora generando i propri elementi DOM e solo una volta.


1

Ecco un mix leggero per la creazione e il rendering di sottoview, che penso risolva tutti i problemi di questo thread:

https://github.com/rotundasoftware/backbone.subviews

L'approccio adottato da questo plug-in è la creazione e il rendering di visualizzazioni secondarie dopo il primo rendering della visualizzazione padre. Quindi, nei successivi rendering della vista padre, $. Scollega gli elementi della vista secondaria, esegue nuovamente il rendering della madre, quindi inserisce gli elementi della vista secondaria nelle posizioni appropriate e ricalcola nuovamente. In questo modo gli oggetti delle visualizzazioni secondarie vengono riutilizzati nei rendering successivi e non è necessario delegare nuovamente gli eventi.

Si noti che il caso di una vista della raccolta (in cui ogni modello della raccolta è rappresentato con una sottoview) è piuttosto diverso e credo che valga la propria discussione / soluzione. La migliore soluzione generale di cui sono a conoscenza in questo caso è CollectionView in Marionette .

MODIFICA: per il caso di visualizzazione della raccolta, potresti anche voler dare un'occhiata a questa implementazione più incentrata sull'interfaccia utente , se hai bisogno di selezione di modelli basati su clic e / o trascinamento per il riordino.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.