Backbone.js'de alt görünümleri başlatma ve oluşturma nasıl yapılır?


199

Bir görünümü ve alt görünümlerini başlatmak ve oluşturmak için üç farklı yolum var ve her birinin farklı sorunları var. Tüm sorunları çözmenin daha iyi bir yolu olup olmadığını merak ediyorum:


Birinci Senaryo:

Ebeveynin başlatma işlevindeki alt öğeleri başlatın. Bu şekilde, render işleminde daha az engelleme olması için her şey render işleminde sıkışmaz.

initialize : function () {

    //parent init stuff

    this.child = new Child();
},

render : function () {

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

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

Problemler:

  • En büyük sorun, üst öğe üzerinde ikinci kez render işlemi çağırmanın tüm child olay bağlamalarını kaldırmasıdır. (Bunun nedeni jQuery'nin $.html()çalışmasıdır.) Bunun this.child.delegateEvents().render().appendTo(this.$el);yerine çağrılarak hafifletilebilir , ancak daha sonra ilk ve en sık olarak gereksiz yere daha fazla iş yaparsınız.

  • Çocukları ekleyerek, render işlevini ebeveynlerin DOM yapısı hakkında bilgi sahibi olmaya zorlarsınız, böylece istediğiniz siparişi alırsınız. Bu, bir şablonun değiştirilmesi görünümün oluşturma işlevinin güncellenmesini gerektirebileceği anlamına gelir.


İkinci Senaryo:

Ebeveynin initialize()hareketsiz durumundaki çocukları başlatın , ancak eklemek yerine setElement().delegateEvents()alt öğeyi ebeveyn şablonundaki bir öğeye ayarlamak için kullanın .

initialize : function () {

    //parent init stuff

    this.child = new Child();
},

render : function () {

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

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

sorunlar:

  • Bu delegateEvents()şimdi gerekli kılmaktadır , ki bu sadece ilk senaryoda sonraki çağrılarda gerekli olandan biraz olumsuzdur.

Üçüncü Senaryo:

render()Bunun yerine alt öğeyi ebeveynin yöntemiyle başlatın .

initialize : function () {

    //parent init stuff
},

render : function () {

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

    this.child = new Child();

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

sorunlar:

  • Bu, oluşturma işlevinin artık tüm başlatma mantığına da bağlanması gerektiği anlamına gelir.

  • Alt görünümlerden birinin durumunu düzenler ve ardından üst öğeye render işlemi çağırırsam, tamamen yeni bir çocuk oluşturulur ve geçerli durumunun tümü kaybolur. Bu aynı zamanda bellek sızıntıları için zor olabilir gibi görünüyor.


Çocuklarınızın bunu ele geçirmesini gerçekten çok merak ediyorum. Hangi senaryoyu kullanırdınız? ya da tüm bu problemleri çözen dördüncü büyülü bir problem var mı?

Hiç bir Görünüm için oluşturulmuş bir durumu izlediniz mi? Bir Say renderedBeforebayrak? Gerçekten iğrenç görünüyor.


1
Ben genellikle iletişimin çoğunun modeller / koleksiyonlar ve bunlarda yapılan değişikliklerle tetiklenen olaylar yoluyla gerçekleşmesine neden olur. 3. vaka çoğu zaman kullandığım şeye en yakın olmasına rağmen. Durum 1 mantıklı. Ayrıca çoğu zaman tüm görünümü yeniden değil, değişen kısmı tekrarlamalısınız
Tom Tu

Elbette, mümkün olduğunda kesinlikle sadece değiştirilen parçaları oluşturuyorum, ancak o zaman bile render fonksiyonunun mevcut ve tahribatsız olması gerektiğini düşünüyorum. Ebeveynin çocuklarına referans vermemekle nasıl başa çıkıyorsunuz?
Ian Storm Taylor

Genellikle çocuk görünümleriyle ilişkili modellerde olayları dinlerim - daha özel bir şey yapmak istersem olayları alt görünümlere bağlarım. Bu tür bir 'iletişim' gerekiyorsa, genellikle olayları vb. Bağlayan alt görünümler oluşturmak için genellikle yardımcı bir yöntem oluşturuyorum.
Tom Tu

1
Daha üst düzey, ilgili bir tartışma için bkz . Stackoverflow.com/questions/10077185/… .
Ben Roberts

2
Senaryo Two, neden o aramak gereklidir delegateEvents()sonra setElement()? Dokümanlara göre: "... ve görünümün temsil edilen etkinliklerini eski öğeden yenisine taşıyın", setElementyöntemin kendisi, etkinliklerin yeniden temsil edilmesini gerçekleştirmelidir.
rdamborsky

Yanıtlar:


260

Bu harika bir soru. Omurga yaptığı varsayımların olmaması nedeniyle harikadır, ancak bu, bunun gibi şeyleri kendiniz nasıl uygulayacağınıza (nasıl karar vereceğiniz) anlamına gelir. Kendi eşyalarımı inceledikten sonra, senaryo 1 ve senaryo 2'nin bir karışımını kullandığımı görüyorum. 4. büyülü bir senaryo olduğunu düşünmüyorum çünkü senaryo 1 ve 2'de yaptığınız her şey yapılır.

Bir örnekle nasıl başa çıkmayı sevdiğimi açıklamanın en kolay olacağını düşünüyorum. Diyelim ki bu basit sayfa belirtilen görünümlere ayrılmış:

Sayfa Dağılımı

HTML'nin işlendikten sonra şöyle bir şey olduğunu varsayalım:

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

Umarım HTML'nin diyagramla nasıl eşleştiği oldukça açıktır.

ParentView2 çocuk manzaraları, tutar InfoViewve PhoneListView, bunlardan biri de birkaç ekstra div'leri, #namebir noktada ayarlanması gerekir. PhoneListViewkendi alt görünümlerini, bir dizi PhoneViewgirdiyi tutar.

Yani asıl sorunuza. Başlatma ve oluşturma işlemlerini görünüm türüne göre farklı şekilde ele alıyorum. Görüşlerimi iki türe ayırıyorum, Parentgörünümler ve Childgörünümler.

Aralarındaki fark basittir, Parentgörünümler alt görünümleri tutarken Childgörünümler tutmaz . Yani benim örnekte, ParentViewve PhoneListViewvardır Parentederken, görünümler InfoViewve PhoneViewgirişleri vardır Childgörünümleri.

Daha önce de belirttiğim gibi, bu iki kategori arasındaki en büyük fark, oluşturulmalarına izin verildiğidir. Mükemmel bir dünyada, Parentgörüntülerin yalnızca bir kez oluşturulmasını istiyorum . Model (ler) değiştiğinde herhangi bir yeniden oluşturma işleminin gerçekleştirilmesi çocuklarının görüşlerine bağlıdır. Childdiğer yandan, onlara ihtiyaç duydukları başka görüşlere sahip olmadıkları için ihtiyaç duydukları her an yeniden oluşturmalarına izin veriyorum.

Biraz daha ayrıntılı olarak, Parentgörünümler için initializeişlevlerimin birkaç şey yapmasını istiyorum :

  1. Kendi görüşümü başlat
  2. Kendi görüşümü oluştur
  3. Alt görünümler oluşturun ve başlatın.
  4. Her alt görünümü benim görüşümdeki bir öğeye atayın (örneğin InfoViewatanır #info).

Adım 1 oldukça açıklayıcıdır.

Adım 2, oluşturma işlemi, atamaya çalışmadan önce çocuğun görüntülediği tüm öğelerin zaten varolması için yapılır. Bunu yaparak, tüm çocukların eventsdoğru şekilde ayarlanacağını biliyorum ve herhangi bir şeyi yeniden devretmek zorunda kalmadan endişelerini duymadan bloklarını istediğim kadar tekrar oluşturabilirim. Aslında renderburada hiçbir çocuk görüşüm yok, bunu kendi başlarına yapmalarına izin veriyorum initialization.

Adım 3 ve 4 aslında elalt görünümü oluştururken geçerken ele alınır . Buraya bir öğe iletmeyi seviyorum, çünkü ebeveynin kendi görüşüne göre çocuğun içeriğini nereye koymasına izin verildiğini belirlemesi gerektiğini hissediyorum.

ParentGörüntüleme için, görünümler için oldukça basit tutmaya çalışıyorum . renderFonksiyonun üst görünümden başka bir şey yapmamasını istiyorum . Etkinlik yetkisi yok, çocuk görüşlerinin görüntülenmesi yok, hiçbir şey yok. Sadece basit bir render.

Bazen bu her zaman işe yaramaz. Örneğin yukarıdaki #nameörneğimde, modeldeki ad değiştiğinde öğenin güncellenmesi gerekecektir. Ancak, bu blok ParentViewşablonun bir parçasıdır ve adanmış bir Childgörünüm tarafından ele alınmaz , bu yüzden bunun etrafında çalışırım. Yalnızca öğenin içeriğinin yerini alan ve tüm öğeyi çöpe atmak zorunda olmayan bir tür subRenderişlev oluşturacağım . Bu bir hack gibi görünebilir, ancak gerçekten tüm DOM'u yeniden oluşturma ve öğeleri yeniden bağlama endişesi olmaktan daha iyi çalıştığını gördüm. Gerçekten temiz yapmak isteseydim , bloğu kaldıracak yeni bir görünüm yaratırdım .#name#parentChildInfoView#name

Şimdi Childgörünümler için, daha fazla görünüm oluşturmadan, görünümlere initializationoldukça benzer . Yani:ParentChild

  1. Görüşümü başlat
  2. Kurulum, önem verdiğim modelde yapılan değişiklikleri dinlemeyi bağlar
  3. Görüşümü oluştur

Childgörüntüleme oluşturma da çok basit, sadece benim içeriğini oluşturmak ve ayarlamak el. Yine, delegasyonla veya bunun gibi bir şeyle uğraşmak yok.

İşte benim nasıl ParentViewgörünebilir bazı örnek kod :

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

Uygulamamı subRenderburada görebilirsiniz . Bunun subRenderyerine değişikliklere bağlı olarak render, tüm bloğu patlatmak ve yeniden inşa etmek konusunda endişelenmem gerekmiyor.

İşte InfoViewblok için örnek kod :

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

Bağlar burada önemli bir parçadır. Modelime bağlanarak asla renderkendimi manuel olarak aramaktan endişelenmem gerekmiyor . Model değişirse, bu blok diğer görünümleri etkilemeden kendini yeniden oluşturur.

PhoneListViewBenzer olacaktır ParentView, sadece hem de biraz daha mantık gerekir initializationve rendersap koleksiyonlarına fonksiyonlar. Koleksiyonu nasıl ele alacağınız tamamen size bağlıdır, ancak en azından koleksiyon etkinliklerini dinlemeniz ve nasıl oluşturmak istediğinize karar vermeniz gerekir (tüm bloğu ekleyin / kaldırın veya yalnızca yeniden oluşturun). Şahsen yeni görünümler eklemeyi ve eski görünümleri kaldırmayı seviyorum, tüm görünümü yeniden oluşturmuyorum.

PhoneViewNeredeyse aynı olacak InfoViewancak bunun umurunda model değişiklikleri dinleyerek.

Umarım bu biraz yardımcı olmuştur, lütfen bir şeyin kafa karıştırıcı olup olmadığını veya yeterince ayrıntılı olmadığını bildirin.


8
Gerçekten ayrıntılı bir açıklama teşekkürler. Yine de soru render, initializeyöntemin içinde aramanın kötü bir uygulama olduğunu duydum ve kabul etme eğilimindeyim , çünkü hemen render etmek istemediğiniz durumlarda daha fazla performans göstermenizi engelliyor. Bunun hakkında ne düşünüyorsun?
Ian Storm Taylor

Elbette. Hmm ... Yalnızca şu anda gösterilmesi gereken görünümlerin başlatılmasını sağlamaya çalışıyorum (veya yakın gelecekte gösterilebilir, örneğin şimdi gizlenmiş ancak bir düzenleme bağlantısını tıkladıktan sonra gösterilen bir düzenleme formu), bu yüzden hemen işlenmelidir. Bir görünümün oluşturulmasının biraz zaman aldığı bir durumum varsa, görünmesi gerekene kadar kişisel olarak görünümün kendisini oluşturmuyorum, bu yüzden gerekli olana kadar başlatma ve oluşturma gerçekleşmeyecek. Bir örneğiniz varsa, bununla nasıl başa çıkacağım hakkında daha fazla ayrıntıya girmeye çalışabilirim ...
Kevin Peel

5
Bir ParentView'ı yeniden oluşturamama büyük bir sınırlamadır. Temelde her sekme farklı bir ParentView gösterir bir sekme denetimi var bir durum var. Bir sekme ikinci kez tıklatıldığında sadece mevcut ParentView yeniden oluşturmak istiyorum, ancak bunu yapmak ChildViews olayları kaybolmasına neden olur (Ben init içinde çocuk oluşturmak ve render render). Sanırım ya 1) render yerine gizlemek / göstermek, 2) ChildViews'ın render'ındaki delegateEvents veya 3) render içinde çocuklar oluşturmak veya 4) perf için endişelenmeyin. Bu sorun olmadan önce ve her seferinde ParentView'ı yeniden oluşturun. Hmmmm ....
Paul Hoenecke

Benim durumumda bir sınırlama olduğunu söylemeliyim. Ebeveyni yeniden oluşturmaya çalışmazsanız bu çözüm iyi çalışır :) İyi bir cevabım yok ama OP ile aynı sorum var. Hala uygun 'Omurga' yolunu bulmayı umuyorum. Bu noktada saklambaç / gösteri düşünüyorum ve yeniden oluşturma benim için bir yol.
Paul Hoenecke

1
koleksiyonu çocuğa nasıl geçirirsiniz PhoneListView?
Tri Nguyen


6

Bana göre, intital kurulum ve sonraki görünümler arasında bir tür bayrakla ayrım yapmak dünyanın en kötü fikri gibi görünmüyor. Bunu temiz ve kolay hale getirmek için bayrak, Omurga (Taban) Görünümünü genişletecek olan kendi Görünümünüze eklenmelidir.

Derick ile aynı, bunun doğrudan sorunuza cevap verip vermediğinden tam olarak emin değilim ama en azından bu bağlamda bahsetmeye değer olabilir.

Ayrıca bakınız: Omurgada bir Eventbus kullanımı


3

Kevin Peel harika bir cevap veriyor - işte benim tl; dr sürümü:

initialize : function () {

    //parent init stuff

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

    this.child = new Child();
},

2

Ben böyle görüşler arasında bağlantı önlemek için çalışıyorum. Genelde iki yöntemim var:

Bir yönlendirici kullanın

Temel olarak, yönlendiricinizin üst ve alt görünümünü başlatmasına izin verirsiniz. Bu yüzden görünüm birbiriyle ilgili hiçbir bilgiye sahip değildir, ancak yönlendirici her şeyi ele alır.

Aynı elin her iki görüşe de geçmesi

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

Her ikisi de aynı DOM hakkında bilgi sahibi ve istediğiniz gibi sipariş edebilirsiniz.


2

Yaptığım her çocuğa bir kimlik vermek (Omurga bunu sizin için zaten yaptı: cid)

Kap oluşturma işlemini gerçekleştirdiğinde, 'cid' ve 'tagName' kullanarak her çocuk için bir yer tutucu oluşturur, bu nedenle şablonda çocukların Kap tarafından nereye yerleştirileceği hakkında hiçbir fikri yoktur.

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

kullanabileceğinden daha fazla

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

belirtilen bir yer tutucusuna gerek yoktur ve Kapsayıcı, çocukların DOM yapısı yerine yalnızca yer tutucuyu oluşturur. Cotainer ve Children, yalnızca bir kez kendi DOM öğelerini oluşturuyor.


1

İşte ben bu konudaki tüm sorunları ele olduğunu düşünüyorum alt görünümleri oluşturmak ve oluşturmak için hafif bir mixin:

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

Bu eklenti tarafından kullanılan yaklaşım , üst görünüm ilk kez oluşturulduktan sonra alt görünümler oluşturur ve oluşturur. Ardından, üst görünümün sonraki görüntülemelerinde $ .dech alt öğe öğelerini yeniden oluşturun, üst öğeyi yeniden oluşturun, ardından alt görünüm öğelerini uygun yerlere yerleştirin ve yeniden oluşturun. Bu şekilde alt görüntüleme nesneleri sonraki oluşturmalarda yeniden kullanılır ve olayları yeniden temsil etmeye gerek yoktur.

Bir koleksiyon görünümünün (koleksiyondaki her modelin bir alt görünümle temsil edildiği) oldukça farklı olduğunu ve kendi tartışma / çözümünü hak ettiğini düşünüyorum. Bu durumun farkında olduğum en iyi genel çözüm , Marionette'deki CollectionView .

DÜZENLEME: Tıklamalara dayalı model seçimine ve / veya yeniden sıralama için sürükleyip bırakmaya ihtiyacınız varsa , koleksiyon görünümü durumu için bu daha kullanıcı arayüzü odaklı uygulamaya bakmak da isteyebilirsiniz .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.