Backbone.js'de alt görünümler nasıl oluşturulur ve eklenir


133

Uygulamamda biraz derinleşen iç içe görünüm kurulumum var. Alt görünümleri başlatmayı, oluşturmayı ve eklemeyi düşünebileceğim birçok yol var, ancak ortak uygulamanın ne olduğunu merak ediyorum.

İşte düşündüğüm bir çift:

initialize : function () {

    this.subView1 = new Subview({options});
    this.subView2 = new Subview({options});
},

render : function () {

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

    this.subView1.setElement('.some-el').render();
    this.subView2.setElement('.some-el').render();
}

Artıları: Ekleyerek doğru DOM sırasını korumak konusunda endişelenmenize gerek yok. Görünümler erken başlatılır, bu nedenle oluşturma işlevinde bir kerede yapılacak çok fazla şey yoktur.

Eksileri: Eğer pahalı olabilir yeniden delegateEvents () zorla? Üst görünümün oluşturma işlevi, gerçekleşmesi gereken tüm alt görünüm oluşturma işlemiyle karışık mı? tagNameÖğeleri ayarlama olanağınız yok , bu nedenle şablonun doğru tagNames'i tutması gerekiyor.

Diğer yol:

initialize : function () {

},

render : function () {

    this.$el.empty();

    this.subView1 = new Subview({options});
    this.subView2 = new Subview({options});

    this.$el.append(this.subView1.render().el, this.subView2.render().el);
}

Artıları: Etkinlikleri yeniden temsil etmek zorunda değilsiniz. Yalnızca boş yer tutucuları içeren bir şablona ihtiyacınız yoktur ve tagName'leriniz görünüm tarafından tanımlanmaya geri döner.

Eksileri: Artık doğru sırayla bir şeyler eklediğinizden emin olmalısınız. Üst görünümün görüntüsü, alt görünümün oluşturulmasıyla hala karışık.

Bir onRenderetkinlikle:

initialize : function () {
    this.on('render', this.onRender);
    this.subView1 = new Subview({options});
    this.subView2 = new Subview({options});
},

render : function () {

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

    //other stuff

    return this.trigger('render');
},

onRender : function () {

    this.subView1.setElement('.some-el').render();
    this.subView2.setElement('.some-el').render();
}

Artıları: Alt görünüm mantığı artık görünümün render()yönteminden ayrılmıştır .

Bir onRenderetkinlikle:

initialize : function () {
    this.on('render', this.onRender);
},

render : function () {

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

    //other stuff

    return this.trigger('render');
},

onRender : function () {
    this.subView1 = new Subview();
    this.subView2 = new Subview();
    this.subView1.setElement('.some-el').render();
    this.subView2.setElement('.some-el').render();
}

Bu örneklerin birçoğunu farklı şekillerde karıştırdım ve eşleştirdim (bunun için çok üzgünüm), ancak saklayacağınız veya ekleyeceğiniz şeyler neler? ve ne yapmazdın?

Uygulamaların özeti:

  • Alt görünümler içinde initializemi, içinde rendermi?
  • Tüm alt görünüş render mantığı gerçekleştir renderveya onRender?
  • Kullan setElementveya append/appendTo?

Yeni silmeden dikkatli olurdum, orada bellek sızıntısı var.
vimdude

1
Endişelenme, çocukları temizleyen bir closeyöntemim ve bir yöntemim var onClose, ama sadece onları nasıl başlatacağımızı ve ilk etapta nasıl oluşturacağımı merak ediyorum.
Ian Storm Taylor

3
@abdelsaid: JavaScript'te GC, belleğin yeniden yerleştirilmesini yönetir. deleteJS de deleteC ++ ile aynı değildir . Bana sorarsanız çok kötü adlandırılmış bir anahtar kelime.
Mike Bailey

@MikeBantegui anladım ama JS'de hafızada yer açmak için sadece null atamanız dışında java ile aynı. Ne demek istediğimi açıklığa kavuşturmak için, içinde yeni bir nesne bulunan bir döngü oluşturmayı ve belleği izlemeyi deneyin. Tabii ki GC ona ulaşacak, ancak onu almadan önce hafızayı kaybedeceksiniz. Bu durumda birçok kez çağrılabilecek hale getirin.
vimdude

3
Ben acemi bir Omurga geliştiricisiyim. Örnek 1 bizi neden yeniden temsilci seçmeye zorladığını açıklayabilir mi? (Yoksa bunu kendi sorusunda sormalıyım?) Teşekkürler.
pilau

Yanıtlar:


58

Genel olarak birkaç farklı çözüm gördüm / kullandım:

Çözüm 1

var OuterView = Backbone.View.extend({
    initialize: function() {
        this.inner = new InnerView();
    },

    render: function() {
        this.$el.html(template); // or this.$el.empty() if you have no template
        this.$el.append(this.inner.$el);
        this.inner.render();
    }
});

var InnerView = Backbone.View.extend({
    render: function() {
        this.$el.html(template);
        this.delegateEvents();
    }
});

Bu, ilk örneğinize benzer, birkaç değişiklikle:

  1. Alt öğeleri ekleme sırası önemlidir
  2. Dış görünüm, iç görünümde / görünümlerde ayarlanacak html öğelerini içermez (yani, iç görünümde tagName'i belirtebileceğiniz anlamına gelir)
  3. render()iç görünümün öğesi DOM'a yerleştirildikten SONRA çağrılır; bu, iç görünümünüzün render()yöntemi, diğer öğelerin konum / boyutuna (benim deneyimime göre ortak bir kullanım örneği olan) göre kendini sayfaya yerleştiriyorsa / boyutlandırıyorsa yardımcı olur

Çözüm 2

var OuterView = Backbone.View.extend({
    initialize: function() {
        this.render();
    },

    render: function() {
        this.$el.html(template); // or this.$el.empty() if you have no template
        this.inner = new InnerView();
        this.$el.append(this.inner.$el);
    }
});

var InnerView = Backbone.View.extend({
    initialize: function() {
        this.render();
    },

    render: function() {
        this.$el.html(template);
    }
});

Çözüm 2 daha temiz görünebilir, ancak deneyimimde bazı garip şeylere neden oldu ve performansı olumsuz etkiledi.

Genellikle Çözüm 1'i birkaç nedenden dolayı kullanıyorum:

  1. Görüşlerimin çoğu zaten render()yöntemlerinde DOM'de olmaya güveniyor
  2. Dış görünüm yeniden oluşturulduğunda, görünümlerin yeniden başlatılması gerekmez, bu yeniden başlatma bellek sızıntılarına neden olabilir ve ayrıca mevcut bağlantılarda garip sorunlara neden olabilir

Eğer bir başlatılıyor eğer unutmayın new View()her zaman render()denir ki başlatma arayacak delegateEvents()zaten. Bu, ifade ettiğiniz gibi, mutlaka bir "aleyhte" olmamalıdır.


1
Bu çözümlerin hiçbiri, görünümde özel temizleme yaparken hayati önem taşıyan View.remove adlı alt görünüm ağacını çalıştırmaz, aksi takdirde çöp toplanmasını önler
Dominic

31

Bu, Omurga ile ilgili çok yıllık bir problem ve benim tecrübelerime göre, bu soruya gerçekten tatmin edici bir cevap yok. Hayal kırıklığınızı paylaşıyorum, çünkü bu kullanım durumunun ne kadar yaygın olmasına rağmen çok az rehberlik var. Bununla birlikte, genellikle ikinci örneğinize benzer bir şeyle giderim.

Her şeyden önce, olayları yeniden delege etmenizi gerektiren her şeyi elden alırım. Omurganın olay güdümlü görünüm modeli, en önemli bileşenlerinden biridir ve uygulamanız önemsiz olduğu için bu işlevselliği kaybetmek, herhangi bir programcının ağzında kötü bir tat bırakacaktır. Yani birinci çizik.

Üçüncü örneğinizle ilgili olarak, bence bu sadece geleneksel oluşturma pratiği etrafında bir son ve çok fazla anlam katmıyor. Belki de gerçek olay tetikleme (yani, bir " onRender" olay "değil) yapıyorsanız , bu olayları renderkendisine bağlamaya değer . Eğer renderhantal ve karmaşık olduğunu fark ederseniz, çok az alt görüşünüz olur.

Muhtemelen üç kötülükten daha az olan ikinci örneğinize geri dönün. İşte PDF sürümümün 42. sayfasında bulunan Omurgaya Sahip Tarifler'den alınan örnek kod :

...
render: function() {
    $(this.el).html(this.template());
    this.addAll();
    return this;
},
  addAll: function() {
    this.collection.each(this.addOne);
},
  addOne: function(model) {
    view = new Views.Appointment({model: model});
    view.render();
    $(this.el).append(view.el);
    model.bind('remove', view.remove);
}

Bu yalnızca ikinci örnekte göre biraz daha sofistike kurulum: onlar fonksiyonları, bir dizi specifiy addAllve addOnekirli işler yapmak. Bu yaklaşımın uygulanabilir olduğunu düşünüyorum (ve kesinlikle kullanıyorum); ama yine de tuhaf bir tat bırakıyor. (Bütün bu dil metaforlarını affedin.)

Doğru sırayla ekleme noktasında: kesinlikle ekliyorsanız, bu bir sınırlamadır. Ancak tüm olası şablon düzenlerini göz önünde bulundurduğunuzdan emin olun. Belki de daha sonra uygun alt görünümleri tutan yeni (DOM) bir öğe yapabileceğiniz bir yer tutucu öğe (ör. Boş divveya ul) replaceWithistersiniz. Eklemek tek çözüm değildir ve bu kadar önemsiyorsanız sipariş sorununu kesinlikle çözebilirsiniz, ancak sizi açıyorsa bir tasarım sorununuz olduğunu hayal ediyorum. Unutmayın, alt görüntülerin alt görünümleri olabilir ve uygunsa gerekir. Bu şekilde, oldukça hoş bir ağaç benzeri yapıya sahip olursunuz: her alt görünüm, üst görünüm başka bir öğe eklemeden önce tüm alt görünümlerini sırayla ekler.

Ne yazık ki, çözüm # 2 muhtemelen kutudan çıkmış bir Omurga kullanmak için umut edebileceğiniz en iyisidir. Üçüncü taraf kitaplıklarını kontrol etmekle ilgileniyorsanız, baktığım (ancak henüz oynamak için hiç vaktim olmadı) biri , daha sağlıklı bir alt görünüm ekleme yöntemi gibi görünen Backbone.LayoutManager . Ancak, son zamanlarda bunlara benzer konularda tartışmalar yaşadı .


4
Sondan bir önceki satır - model.bind('remove', view.remove);- sadece Randevunun başlatma işlevinde onları ayrı tutmak için yapmamalısınız?
atp

2
Bir durumu, ana durumu koruduğu için her görüntüleyişinde yeniden başlatılamadığında ne olur?
mor

Tüm bu deliliği durdurun ve sadece Backbone.subviews eklentisini kullanın !
Cesur Dave

6

Bu henüz belirtilmedi şaşırttı, ama ben ciddi Marionette kullanmayı düşünün .

Belirli görünüm türleri (dahil omurga uygulamalar için biraz daha yapıya zorlar ListView, ItemView, Regionve Layout), düzgün ekleme Controllers ve daha bir çok.

İşte Github projesi ve Addy Osmani'nin Backbone Fundamentals kitabında başlamanız için harika bir rehber .


3
Bu soruya cevap vermiyor.
Ceasar Bautista

2
@CeasarBautista Bunu başarmak için Marionette'i nasıl kullanacağım ama Marionette gerçekten yukarıdaki sorunu çözüyor
Dana Woodman

4

Olduğuma inandığım bu soruna oldukça kapsamlı bir çözüm buldum. Bir koleksiyondaki bir modelin değişmesine izin verir ve yalnızca görünümünün (koleksiyonun tamamı yerine) yeniden oluşturulmasını sağlar. Ayrıca zombi görünümlerinin close () yöntemleriyle kaldırılmasını da sağlar.

var SubView = Backbone.View.extend({
    // tagName: must be implemented
    // className: must be implemented
    // template: must be implemented

    initialize: function() {
        this.model.on("change", this.render, this);
        this.model.on("close", this.close, this);
    },

    render: function(options) {
        console.log("rendering subview for",this.model.get("name"));
        var defaultOptions = {};
        options = typeof options === "object" ? $.extend(true, defaultOptions, options) : defaultOptions;
        this.$el.html(this.template({model: this.model.toJSON(), options: options})).fadeIn("fast");
        return this;
    },

    close: function() {
        console.log("closing subview for",this.model.get("name"));
        this.model.off("change", this.render, this);
        this.model.off("close", this.close, this);
        this.remove();
    }
});
var ViewCollection = Backbone.View.extend({
    // el: must be implemented
    // subViewClass: must be implemented

    initialize: function() {
        var self = this;
        self.collection.on("add", self.addSubView, self);
        self.collection.on("remove", self.removeSubView, self);
        self.collection.on("reset", self.reset, self);
        self.collection.on("closeAll", self.closeAll, self);
        self.collection.reset = function(models, options) {
            self.closeAll();
            Backbone.Collection.prototype.reset.call(this, models, options);
        };
        self.reset();
    },

    reset: function() {
        this.$el.empty();
        this.render();
    },

    render: function() {
        console.log("rendering viewcollection for",this.collection.models);
        var self = this;
        self.collection.each(function(model) {
            self.addSubView(model);
        });
        return self;
    },

    addSubView: function(model) {
        var sv = new this.subViewClass({model: model});
        this.$el.append(sv.render().el);
    },

    removeSubView: function(model) {
        model.trigger("close");
    },

    closeAll: function() {
        this.collection.each(function(model) {
            model.trigger("close");
        });
    }
});

Kullanımı:

var PartView = SubView.extend({
    tagName: "tr",
    className: "part",
    template: _.template($("#part-row-template").html())
});

var PartListView = ViewCollection.extend({
    el: $("table#parts"),
    subViewClass: PartView
});

2

Alt görünüm oluşturmak ve oluşturmak için bu karışıma göz atın:

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

Oluşturma sırası, olayları yeniden temsil etmek zorunda kalmamak, vb. Dahil olmak üzere bu iş parçacığında ele alınan birçok sorunu ele alan minimalist bir çözümdür. Bir koleksiyon görünümü örneğinde (koleksiyondaki her modelin alt görünüm) farklı bir konudur. Bu durumun farkında olduğum en iyi genel çözüm , Marionette'deki CollectionView .


0

Yukarıdaki çözümlerden hiçbirini gerçekten sevmiyorum. Bu yapılandırma için render yönteminde manuel olarak çalışmak zorunda olan her görünüm üzerinde tercih ederim.

  • views görünüm tanımları nesnesini döndüren bir işlev veya nesne olabilir
  • Bir ebeveyn .removeçağrıldığında, en .removedüşük sıradaki yuvalanmış çocukların çağrılması gerekir (alt-alt-alt görünümlerden sonuna kadar)
  • Varsayılan olarak üst görünüm kendi modelinden ve koleksiyonundan geçer, ancak seçenekler eklenebilir ve geçersiz kılınabilir.

İşte bir örnek:

views: {
    '.js-toolbar-left': CancelBtnView, // shorthand
    '.js-toolbar-right': {
        view: DoneBtnView,
        append: true
    },
    '.js-notification': {
        view: Notification.View,
        options: function() { // Options passed when instantiating
            return {
                message: this.state.get('notificationMessage'),
                state: 'information'
            };
        }
    }
}

0

Omurga kasıtlı olarak inşa edildi, böylece bu ve diğer birçok konuda "ortak" uygulama yoktu. Mümkün olduğunca açılmamış olması amaçlanmıştır. Teorik olarak, Omurga ile şablonlar kullanmak zorunda bile değilsiniz. renderGörünümdeki tüm verileri el ile değiştirmek için görünüm işlevinde javascript / jquery kullanabilirsiniz . Daha uç bir nokta yapmak için belirli bir renderişleve bile ihtiyacınız yok . renderFirstNameDom'daki ilk adı güncelleyen ve dom'daki soyadını güncelleyen adlı bir işleviniz olabilir renderLastName. Bu yaklaşımı uygularsanız, performans açısından çok daha iyi olurdu ve etkinlikleri bir daha asla manuel olarak devretmeniz gerekmez. Kod aynı zamanda onu okuyan birisine de anlamlı gelecektir (daha uzun / daha karışık bir kod olsa da).

Bununla birlikte, genellikle şablonları kullanmanın ve tüm görünümü yok etmenin ve yeniden oluşturmanın bir dezavantajı yoktur ve sorgulayıcıda başka bir şey yapması için bile gerçekleşmediği için her render çağrısında alt görünümler vardır. Bu yüzden çoğu insan karşılaştıkları her durum için bunu yapar. Ve bu yüzden değerlendirilmiş çerçeveler bunu varsayılan davranış haline getirir.


0

Ayrıca, oluşturulan alt görünümleri değişken olarak ana şablona değişken olarak enjekte edebilirsiniz.

önce alt görünümleri oluşturun ve bunları şu şekilde html'ye dönüştürün:

var subview1 = $(subview1.render.el).html(); var subview2 = $(subview2.render.el).html();

(bu şekilde görünümleri dinamik bir şekilde dizeleyebilirsiniz subview1 + subview2 döngülerde kullanıldığında olduğu olarak dizeleyebilirsiniz) ve ardından aşağıdaki gibi görünen ana şablona iletebilirsiniz: ... some header stuff ... <%= sub1 %> <%= sub2 %> ... some footer stuff ...

ve sonunda şu şekilde enjekte edin:

this.$el.html(_.template(MasterTemplate, { sub1: subview1, sub2: subview2 } ));

Alt görünümlerdeki Olaylar ile ilgili olarak: Büyük olasılıkla alt görünümlerde yer almayan bu yaklaşımla üst öğeye (masterView) bağlanmaları gerekecektir.


0

Çocuk görüşlerini doğru şekilde kaldırdığınızdan emin olmak için aşağıdaki yaklaşımı kullanmak istiyorum. İşte Addy Osmani'nin kitabından bir örnek .

Backbone.View.prototype.close = function() {
    if (this.onClose) {
        this.onClose();
    }
    this.remove(); };

NewView = Backbone.View.extend({
    initialize: function() {
       this.childViews = [];
    },
    renderChildren: function(item) {
        var itemView = new NewChildView({ model: item });
        $(this.el).prepend(itemView.render());
        this.childViews.push(itemView);
    },
    onClose: function() {
      _(this.childViews).each(function(view) {
        view.close();
      });
    } });

NewChildView = Backbone.View.extend({
    tagName: 'li',
    render: function() {
    } });

0

Maliyetli olduğu için etkinliklerin yeniden temsil edilmesine gerek yoktur. Aşağıya bakınız:

    var OuterView = Backbone.View.extend({
    initialize: function() {
        this.inner = new InnerView();
    },

    render: function() {
        // first detach subviews            
        this.inner.$el.detach(); 

        // now can set html without affecting subview element's events
        this.$el.html(template);

        // now render and attach subview OR can even replace placeholder 
        // elements in template with the rendered subview element
        this.$el.append(this.inner.render().el);

    }
});

var InnerView = Backbone.View.extend({
    render: function() {
        this.$el.html(template);            
    }
});
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.