javascript - Hvordan håndtere initialisering og rendering av delvisninger i Backbone.js?


Jeg har tre forskjellige måter å initialisere og rendre en visning og dens underordnede visninger på, og hver av dem har ulike problemer. Jeg er nysgjerrig på om det finnes en bedre måte som løser alle problemene på:

Scenario En:

Initialiser barna i foreldres initialiseringsfunksjon. På denne måten blir ikke alt sittende fast i rendering, slik at det blir mindre blokkering under rendering.

initialize : function () {

    //parent init stuff

    this.child = new Child();
},

render : function () {

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

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

Problemene:

  • Det største problemet er at å kalle render på forelderen for andre gang vil fjerne alle barnets hendelsesbindinger. (Dette skyldes hvordan jQuerys $.html() fungerer.) Dette kunne dempes ved å kalle this.child.delegateEvents().render().appendTo(this.$el); i stedet, men da vil du, i det første og mest vanlige tilfellet, gjøre mer arbeid unødvendig.

  • Ved å legge til barna tvinger du render-funksjonen til å ha kunnskap om foreldrenes DOM-struktur slik at du får rekkefølgen du ønsker. Dette betyr at endring av en mal kan kreve oppdatering av en visnings render-funksjon.

Scenario To:

Initialiser barna i forelderens initialize() fortsatt, men i stedet for å legge til, bruk setElement().delegateEvents() for å sette barnet til et element i foreldres mal.

initialize : function () {

    //parent init stuff

    this.child = new Child();
},

render : function () {

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

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

Problemer:

  • Dette gjør at delegateEvents() nå er nødvendig, noe som er en liten ulempe sammenlignet med at det kun var nødvendig ved påfølgende kall i det første scenariet.

Scenario Tre:

Initialiser barna i foreldrenes render()-metode i stedet.

initialize : function () {

    //parent init stuff
},

render : function () {

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

    this.child = new Child();

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

Problemer:

  • Dette betyr at render-funksjonen nå også må knyttes til all initialiseringslogikk.

  • Hvis jeg redigerer tilstanden til en av barnevisningene, og deretter kaller render på forelderen, vil et helt nytt barn bli laget og all den nåværende tilstanden vil gå tapt. Noe som også ser ut til å kunne bli risikabelt når det gjelder minnelekkasjer.

Virkelig nysgjerrig på å høre hva dere synes om dette. Hvilket scenario ville dere brukt? Eller finnes det en fjerde magisk en som løser alle disse problemene?

Har du noen gang holdt oversikt over en gjengitt tilstand for en View? Si en renderedBefore-flag? Virker ganske klønete.

Svar #1

Dette er et flott spørsmål. Backbone er flott på grunn av mangelen på antakelser den gjør, men det betyr at du må (bestemme hvordan du skal) implementere ting som dette selv. Etter å ha sett gjennom mine egne ting, oppdager jeg at jeg (på en måte) bruker en blanding av scenario 1 og scenario 2. Jeg tror ikke et fjerde magisk scenario eksisterer fordi, helt enkelt, alt du gjør i scenario 1 og 2 må gjøres.

Jeg tror det vil være enklest å forklare hvordan jeg liker å håndtere det med et eksempel. La oss si at jeg har denne enkle siden delt inn i de spesifiserte visningene:

Si at HTML-en, etter å ha blitt gjengitt, er noe sånt som dette:

<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>

Forhåpentligvis er det ganske åpenbart hvordan HTML samsvarer med diagrammet.

ParentView inneholder 2 barnevisninger, InfoView og PhoneListView, samt noen ekstra div-er, en av dem, #name, må settes på et tidspunkt. PhoneListView holder sine egne barnevisninger, et array av PhoneView-oppføringer.

Så over til ditt faktiske spørsmål. Jeg håndterer initialisering og rendering forskjellig basert på visningstypen. Jeg deler visningene mine inn i to typer, Parent-visninger og Child-visninger.

Forskjellen mellom dem er enkel, Parent-visninger holder barnevisninger mens Child-visninger ikke gjør det. Så i mitt eksempel er ParentView og PhoneListView Parent-visninger, mens InfoView og PhoneView-oppføringene er Child-visninger.

Som jeg nevnte tidligere, er den største forskjellen mellom disse to kategoriene når de får lov til å gjengi. I en perfekt verden vil jeg at Parent-visninger kun skal gjengi én gang. Det er opp til deres underordnede visninger å håndtere eventuelle re-gjengivelser når modellen(e) endres. Child-visninger, derimot, tillater jeg å gjengi når som helst de trenger siden det ikke er noen andre visninger som er avhengige av dem.

I litt mer detalj, for Parent-visninger liker jeg at mine initialize-funksjoner gjør noen få ting:

  1. Initialiser mitt eget syn
  2. Gjengi mitt eget syn
  3. Opprett og initialiser eventuelle underordnede visninger.
  4. Tildel hver undermappe et element i visningen min (f.eks. ville InfoView bli tildelt #info).

Trinn 1 er ganske selvforklarende.

Trinn 2, gjengivelsen, gjøres slik at alle elementer barnevisningene er avhengige av allerede eksisterer før jeg prøver å tilordne dem. Ved å gjøre dette, vet jeg at alle barne-events vil bli korrekt satt, og jeg kan gjengi blokkene deres så mange ganger jeg vil uten å bekymre meg for å måtte delegere noe på nytt. Jeg gjengir ikke faktisk noen barnevisninger her, jeg lar dem gjøre det innenfor sin egen initialization.

Trinn 3 og 4 håndteres faktisk samtidig som jeg sender el inn mens jeg oppretter barnevisningen. Jeg liker å sende inn et element her, da jeg føler at forelderen bør bestemme hvor i sin egen visning barnet får lov til å plassere innholdet sitt.

Når det gjelder rendering, prøver jeg å holde det ganske enkelt for Parent-visninger. Jeg vil at render-funksjonen ikke skal gjøre noe mer enn å rendre parent-visningen. Ingen hendelsesdelegering, ingen rendering av barnevisninger, ingenting. Bare en enkel rendering.

Noen ganger fungerer ikke dette alltid. For eksempel i mitt eksempel ovenfor, må #name-elementet oppdateres hver gang navnet i modellen endres. Men denne blokken er en del av ParentView-malen og håndteres ikke av en dedikert Child-visning, så jeg omgår det. Jeg vil lage en slags subRender-funksjon som kun erstatter innholdet i #name-elementet, og ikke måtte slette hele #parent-elementet. Dette kan virke som en hack, men jeg har virkelig funnet ut at det fungerer bedre enn å måtte bekymre seg om å rendre hele DOM igjen og feste elementene på nytt og slikt. Hvis jeg virkelig ønsket å gjøre det ryddig, ville jeg lage en ny Child-visning (lik InfoView) som ville håndtere #name-blokken.

Nå for Child visninger, er initialiseringen ganske lik Parent visninger, bare uten opprettelsen av flere Child visninger. Så:

  1. Initialiser visningen min
  2. Oppsett binder lytting for alle endringer i modellen jeg bryr meg om
  3. Gjengi mitt syn

Rendering av Child-visningen er også veldig enkelt, bare render og sett innholdet til min el. Igjen, ikke rot med delegering eller noe sånt.

Her er litt eksempel på kode på hvordan min ParentView kan se ut:

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

Du kan se min implementering av subRender her. Ved å ha endringer knyttet til subRender i stedet for render, trenger jeg ikke å bekymre meg for å slette og bygge hele blokken på nytt.

Her er eksempel på kode for InfoView-blokken:

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

Bindene er den viktige delen her. Ved å binde til modellen min, trenger jeg aldri å bekymre meg for å manuelt kalle render selv. Hvis modellen endres, vil denne blokken gjengi seg selv på nytt uten å påvirke noen andre visninger.

PhoneListView vil være lik ParentView, du trenger bare litt mer logikk i både initialization og render-funksjonene dine for å håndtere samlinger. Hvordan du håndterer samlingen er helt opp til deg, men du må i det minste lytte til samlingshendelsene og bestemme hvordan du vil rendre (legge til/fjerne, eller bare rendre hele blokken på nytt). Jeg personlig liker å legge til nye views og fjerne gamle, ikke rendre hele viewen på nytt.

PhoneView vil være nesten identisk med InfoView, bare at den lytter til modellendringene den bryr seg om.

Forhåpentligvis har dette hjulpet litt, vennligst gi meg beskjed hvis noe er forvirrende eller ikke detaljert nok.

Svar #2

Jeg er ikke sikker på om dette direkte svarer på spørsmålet ditt, men jeg tror det er relevant:

http://lostechies.com/derickbailey/2011/10/11/backbone-js-hvordan-få-modellen-for-et-trykket-element/

Konteksten jeg satte opp denne artikkelen i er selvfølgelig annerledes, men jeg tror de to løsningene jeg tilbyr, sammen med fordeler og ulemper ved hver, bør få deg til å bevege deg i riktig retning.

Relaterte artikler