Backbone.js में सब-व्यूज़ को कैसे रेंडर और अपग्रेड करें


133

मेरे पास एक नेस्टेड-व्यू सेटअप है जो मेरे आवेदन में कुछ गहरा हो सकता है। ऐसे तरीकों का एक समूह है जो मैं उप-विचारों को शुरू करने, रेंडर करने और उन्हें जोड़ने के बारे में सोच सकता था, लेकिन मैं सोच रहा हूं कि सामान्य अभ्यास क्या है।

यहाँ एक जोड़ी है जिसके बारे में मैंने सोचा है:

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

पेशेवरों: आपको सही डोम ऑर्डर को बनाए रखने के बारे में चिंता करने की ज़रूरत नहीं है। विचारों को प्रारंभिक रूप से आरंभ किया जाता है, इसलिए रेंडर फ़ंक्शन में सभी को एक साथ करने के लिए उतना नहीं है।

विपक्ष: आप फिर से प्रतिनिधि (), जो महंगा हो सकता है के लिए मजबूर कर रहे हैं? पैरेंट व्यू का रेंडर फंक्शन सबव्यू रेंडरिंग के साथ क्लैट किया जाता है जो कि होना चाहिए? आपके पास tagNameतत्वों को सेट करने की क्षमता नहीं है , इसलिए टेम्पलेट को सही टैगनाम बनाए रखने की आवश्यकता है।

दूसरा रास्ता:

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

पेशेवरों: आपको घटनाओं को फिर से सौंपने की ज़रूरत नहीं है। आपको ऐसे टेम्प्लेट की आवश्यकता नहीं है जिसमें केवल खाली प्लेसहोल्डर हों और आपके टैगनाम में वापस दृश्य द्वारा परिभाषित किया गया हो।

विपक्ष: अब आपको सही क्रम में चीजों को जोड़ना सुनिश्चित करना होगा। पैरेंट व्यू का रेंडर अभी भी सबव्यू रेंडरिंग द्वारा क्लैट किया गया है।

एक onRenderघटना के साथ :

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

पेशेवरों: सबव्यू लॉजिक अब दृश्य की render()विधि से अलग हो गया है ।

एक onRenderघटना के साथ :

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

मैंने इन सभी उदाहरणों में अलग-अलग प्रथाओं के मिश्रण का मिलान किया है (इसलिए इसके बारे में खेद है) लेकिन वे क्या हैं जिन्हें आप रखेंगे या जोड़ेंगे? और आप क्या नहीं करेंगे?

प्रथाओं का सारांश:

  • में initializeया में तुरंत साक्षात्कार render?
  • renderया में सभी उप-दृश्य प्रतिपादन तर्क प्रदर्शन करें onRender?
  • का उपयोग करें setElementया append/appendTo?

मैं हटाए बिना नए के बारे में सावधान रहूंगा, आपको वहां मेमोरी लीक मिल गई।
vimdude

1
चिंता न करें, मेरे पास एक closeतरीका है और जो onCloseबच्चों को साफ करता है, लेकिन मैं सिर्फ इस बात को लेकर उत्सुक हूं कि उन्हें पहले स्थान पर कैसे प्रस्तुत किया जाए।
इयान स्टॉर्म टेलर

3
@abdelsaid: जावास्क्रिप्ट में, जीसी मेमोरी के डीक्लोकेशन को हैंडल करता है। deleteजेएस में deleteC ++ से समान नहीं है । यदि आप मुझसे पूछें तो यह एक बहुत खराब नाम वाला कीवर्ड है।
माइक बेली

@MikeBantegui मिल गया, लेकिन यह जावा में वैसा ही है, जेएस को छोड़कर मुफ्त मेमोरी में आपको बस अशक्त असाइन करने की आवश्यकता है। यह स्पष्ट करने के लिए कि मेरा क्या मतलब है, इसे एक नई वस्तु के साथ एक लूप बनाने की कोशिश करें और स्मृति की निगरानी करें। बेशक जीसी मिल जाएगा, लेकिन इससे पहले कि आप इसे प्राप्त करें, आप मेमोरी को ढीला कर देंगे। इस मामले में रेंडर जिसे कई बार कॉल किया जा सकता है।
vmdude

3
मैं नौसिखिया बैकबोन डेवलपर हूं। क्या कोई कृपया बता सकता है कि उदाहरण 1 हमें घटनाओं को फिर से सौंपने के लिए मजबूर क्यों करता है? (या मुझे यह खुद से पूछना चाहिए?) धन्यवाद।
पिलाऊ

जवाबों:


58

मैंने आम तौर पर विभिन्न समाधानों के एक जोड़े को देखा / उपयोग किया है:

समाधान 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();
    }
});

यह कुछ परिवर्तनों के साथ आपके पहले उदाहरण के समान है:

  1. वह क्रम जिसमें आप उप तत्वों को जोड़ते हैं
  2. बाहरी दृश्य में आंतरिक दृश्य पर सेट किए जाने वाले html तत्व नहीं हैं (जिसका अर्थ है कि आप अभी भी आंतरिक में टैगनाम निर्दिष्ट कर सकते हैं)
  3. render()कहा जाता है कि आंतरिक दृश्य के तत्व को DOM में रखा गया है, जो तब सहायक होता है, जब आपके आंतरिक दृश्य का render()तरीका अन्य तत्वों की स्थिति / आकार (जो मेरे अनुभव में एक सामान्य उपयोग का मामला है) के आधार पर पृष्ठ पर स्वयं को रख / आकार दे रहा है।

समाधान २

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

समाधान 2 क्लीनर लग सकता है, लेकिन इसने मेरे अनुभव में कुछ अजीब चीजें पैदा की हैं और प्रदर्शन को नकारात्मक रूप से प्रभावित किया है।

मैं आमतौर पर समाधान 1 का उपयोग करता हूं, कुछ कारणों से:

  1. मेरे बहुत सारे विचार उनके डोम में पहले से मौजूद होने पर निर्भर करते हैं render() तरीके
  2. जब बाहरी दृश्य को फिर से प्रस्तुत किया जाता है, तो विचारों को फिर से आरंभ नहीं किया जाता है, जो पुनः आरंभ करने से मेमोरी लीक हो सकती है और मौजूदा बाइंडिंग के साथ अजीब मुद्दे भी पैदा कर सकते हैं

ध्यान रखें कि यदि आप new View()हर बार इनिशियलाइज़ कर रहे हैं तो render()कॉल किया जाता है, लेकिन इनिशियलाइज़ेशन किसी भी तरह से कॉल करने वाला delegateEvents()है। जैसा कि आपने व्यक्त किया है, इसलिए जरूरी नहीं कि एक "शंकु" हो।


1
इन समाधानों में से कोई भी उप दृश्य ट्री को कॉल करने का काम नहीं करता है View.remove, जो कि दृश्य में कस्टम सफाई करने में महत्वपूर्ण हो सकता है, जो अन्यथा कचरा संग्रह को रोक देगा
डोमिनिक

31

यह बैकबोन के साथ एक बारहमासी समस्या है और मेरे अनुभव में, वास्तव में इस सवाल का संतोषजनक जवाब नहीं है। मैं आपकी हताशा को साझा करता हूं, खासकर जब से यह उपयोग मामला कितना सामान्य है, इसके बावजूद बहुत कम मार्गदर्शन है। उस ने कहा, मैं आमतौर पर आपके दूसरे उदाहरण के लिए कुछ के साथ जाता हूं।

सबसे पहले, मैं कुछ भी हाथ से खारिज कर दूंगा, जिसके लिए आपको घटनाओं को फिर से सौंपना होगा। बैकबोन का ईवेंट-चालित व्यू मॉडल इसके सबसे महत्वपूर्ण घटकों में से एक है, और यह कार्यक्षमता खोना बस इसलिए कि आपका एप्लिकेशन गैर-तुच्छ है, किसी भी प्रोग्रामर के मुंह में खराब स्वाद छोड़ देगा। तो खरोंच नंबर एक।

आपके तीसरे उदाहरण के बारे में, मुझे लगता है कि यह पारंपरिक रेंडरिंग अभ्यास के इर्द-गिर्द ही चलता है और इससे कोई मतलब नहीं है। शायद अगर आप वास्तविक घटना ट्रिगर कर रहे हैं (यानी, एक आकस्मिक " onRender" घटना नहीं), तो यह उन घटनाओं को renderस्वयं के लिए बाध्य करने के लायक होगा । अगर तुम्हें मिलेrender अनिच्छुक और जटिल होते जा रहे हैं, तो आपके पास बहुत कम साक्षात्कार हैं।

अपने दूसरे उदाहरण पर वापस जाएं, जो संभवतः तीन बुराइयों से कम है। उदाहरण मेरे पीडीएफ संस्करण के पृष्ठ 42 पर पाए जाने वाले व्यंजनों के साथ बैकबोन से उठाया गया उदाहरण कोड है :

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

यह आपके दूसरे उदाहरण की तुलना में केवल थोड़ा अधिक परिष्कृत सेटअप है: वे फ़ंक्शंस का एक सेट निर्दिष्ट करते हैं, addAllऔरaddOne , जो गंदा काम करते हैं। मुझे लगता है कि यह दृष्टिकोण व्यावहारिक है (और मैं निश्चित रूप से इसका उपयोग करता हूं); लेकिन यह अभी भी एक विचित्र aftertaste छोड़ देता है। (इन सभी जीभ रूपकों को क्षमा करें।)

सही क्रम में अपनी बात रखने के लिए: यदि आप कड़ाई से अपील कर रहे हैं, तो सुनिश्चित करें कि यह एक सीमा है। लेकिन सुनिश्चित करें कि आप सभी संभावित टेम्प्लेटिंग योजनाओं पर विचार करें। शायद आप वास्तव में एक प्लेसहोल्डर तत्व (जैसे, एक खाली divया ul) को पसंद करेंगे जिसे आप तब replaceWithएक नया (DOM) तत्व बना सकते हैं, जो उपयुक्त साक्षात्कार रखता है। लागू करना एकमात्र समाधान नहीं है, और आप निश्चित रूप से आदेश देने की समस्या के आसपास हो सकते हैं यदि आप इसके बारे में बहुत परवाह करते हैं, लेकिन मुझे लगता है कि आपके पास एक डिजाइन मुद्दा है अगर यह आपको ट्रिप कर रहा है। याद रखें, साक्षात्कार में साक्षात्कार हो सकते हैं, और यदि उचित हो तो उन्हें करना चाहिए। इस तरह, आपके पास एक पेड़ की तरह की संरचना है, जो काफी अच्छा है: प्रत्येक सबव्यू अपने सभी साक्षात्कारों को जोड़ता है, क्रम में, इससे पहले कि माता-पिता का दृष्टिकोण एक और जोड़ता है, और इसी तरह।

दुर्भाग्य से, समाधान # 2 शायद सबसे अच्छा है जो आप आउट-ऑफ-द-बॉक्स बैकबोन का उपयोग करने की उम्मीद कर सकते हैं। यदि आप तृतीय-पक्ष पुस्तकालयों की जाँच करने में रुचि रखते हैं, तो एक जिसे मैंने देखा है (लेकिन वास्तव में अभी तक खेलने के लिए कोई समय नहीं मिला है) Backbone.LayoutManager है , जो लगता है कि उप-परीक्षाओं को जोड़ने का एक स्वस्थ तरीका है। हालाँकि, यहां तक ​​कि इनसे मिलते-जुलते मुद्दों पर हाल की बहसें भी हुई हैं।


4
प्रायद्वीपीय लाइन - model.bind('remove', view.remove);- क्या आपको अपॉइंटमेंट के इनिशियलाइज़ फंक्शन में उन्हें अलग नहीं रखना चाहिए?
एटीपी

2
जब कोई दृश्य माता-पिता के रेंडर करता है, क्योंकि वह एक राज्य रखता है, तो क्या होगा, जब कोई दृश्य फिर से त्वरित नहीं हो सकता है?
mor

यह सब पागलपन बंद करो और सिर्फ Backbone.subviews प्लगइन का उपयोग करें !
बहादुर दवे

6

हैरानी की बात यह है कि अभी तक उल्लेख नहीं किया गया है, लेकिन मैं गंभीरता से Marionette का उपयोग करने पर विचार करूंगा ।

यह रीढ़ क्षुधा को थोड़ा और संरचना को लागू करता है, विशेष दृश्य प्रकार (सहित ListView, ItemView, Regionऔर Layout), उचित जोड़ने Controllerऔर एक बहुत अधिक।

यहाँ आप शुरू करने के लिए बैकबोन फंडामेंटल्स नामक पुस्तक में गितुब पर प्रोजेक्ट और अडी ओस्मानी द्वारा एक महान मार्गदर्शक है


3
इस सवाल का जवाब नहीं है।
सेसर बॉतिस्ता

2
@CeasarBautista मैं इसे पूरा करने के लिए Marionette का उपयोग करने के तरीके में नहीं जाता, लेकिन Marionette वास्तव में उपरोक्त समस्या को हल करता है
Dana Woodman

4

मेरे पास, जो मैं मानता हूं, इस समस्या का एक व्यापक समाधान है। यह एक संग्रह के भीतर एक मॉडल को बदलने की अनुमति देता है, और केवल इसका दृश्य फिर से गाया जाता है (पूरे संग्रह के बजाय)। यह भी करीब () तरीकों के माध्यम से ज़ोंबी विचारों को हटाने का काम करता है।

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

उपयोग:

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

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

2

सबव्यू बनाने और रेंडर करने के लिए इस मिक्सिन को देखें:

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

यह एक न्यूनतम समाधान है जो इस थ्रेड में चर्चा किए गए बहुत सारे मुद्दों को संबोधित करता है, जिसमें रेंडरिंग ऑर्डर, पुन: प्रतिनिधि घटनाओं को शामिल नहीं करना आदि शामिल हैं। ध्यान दें कि संग्रह दृश्य का मामला (जहां संग्रह में प्रत्येक मॉडल को एक के साथ दर्शाया गया है। सबव्यू) एक अलग विषय है। सबसे अच्छा सामान्य समाधान मुझे इस बात की जानकारी है कि मैरियनेट में संग्रह दृश्य है


0

मैं वास्तव में उपरोक्त किसी भी समाधान को पसंद नहीं करता हूं। मैं इस दृष्टिकोण के लिए प्रत्येक दृश्य पर मैन्युअल रूप से रेंडर विधि में काम करने के लिए पसंद करते हैं।

  • views एक फ़ंक्शन या ऑब्जेक्ट हो सकता है जो दृश्य परिभाषाओं को लौटाता है
  • जब एक माता-पिता .removeको बुलाया जाता है, तो .removeसबसे कम क्रम वाले नेस्टेड बच्चों को बुलाया जाना चाहिए (सभी उप-उप-उप विचार से अलग)
  • डिफ़ॉल्ट रूप से पेरेंट व्यू पास यह स्वयं का मॉडल और संग्रह है, लेकिन विकल्प जोड़े और ओवरराइड किए जा सकते हैं।

यहाँ एक उदाहरण है:

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

बैकबोन जानबूझकर बनाया गया था ताकि इस और कई अन्य मुद्दों के संबंध में कोई "सामान्य" अभ्यास न हो। यह संभव के रूप में unopinionated होने के लिए है। सैद्धांतिक रूप से, आपको बैकबोन के साथ टेम्प्लेट का उपयोग करने की आवश्यकता नहीं है। आप renderमैन्युअल रूप से दृश्य में सभी डेटा को परिवर्तित करने के लिए किसी दृश्य के कार्य में जावास्क्रिप्ट / jquery का उपयोग कर सकते हैं । इसे और अधिक चरम बनाने के लिए, आपको एक विशिष्ट renderफ़ंक्शन की भी आवश्यकता नहीं है। आपके पास एक फ़ंक्शन हो सकता है renderFirstNameजो डोम में पहला नाम renderLastNameअपडेट करता है और जो डोम में अंतिम नाम को अपडेट करता है। यदि आपने यह तरीका अपनाया है, तो यह प्रदर्शन के मामले में बेहतर होगा और आपको कभी भी घटनाओं को मैन्युअल रूप से नहीं सौंपना होगा। इस कोड को पढ़ने वाले किसी व्यक्ति के लिए भी इसका पूरा अर्थ होगा (हालाँकि यह अधिक लंबा / गड़बड़ कोड होगा)।

हालाँकि, आमतौर पर टेम्प्लेट का उपयोग करने और केवल संपूर्ण दृश्य को नष्ट करने और पुनर्निर्माण करने के लिए कोई नकारात्मक पहलू नहीं होता है और यह प्रत्येक और हर रेंडर कॉल पर साक्षात्कार होता है, क्योंकि यह प्रश्नकर्ता को अन्यथा कुछ भी करने के लिए नहीं हुआ था। तो यह है कि ज्यादातर लोग हर स्थिति के लिए बहुत कुछ करते हैं। और इसीलिए ओपेरेटेड फ्रेमवर्क बस इसे डिफ़ॉल्ट व्यवहार बनाते हैं।


0

आप प्रदान किए गए साक्षात्कारों को चर के रूप में मुख्य टेम्पलेट में चर के रूप में भी इंजेक्ट कर सकते हैं।

पहले सबव्यू प्रस्तुत करें और उन्हें इस तरह html में बदलें:

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

(इस तरह आप भी गतिशील रूप से स्ट्रिंग जैसे विचारों को बदल सकते हैं subview1 + subview2 कि लूप में इस्तेमाल होने पर) और फिर इसे मास्टर टेम्पलेट में पास करें जो इस तरह दिखता है: ... some header stuff ... <%= sub1 %> <%= sub2 %> ... some footer stuff ...

और अंत में इसे इस तरह इंजेक्ट करें:

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

सबव्यू के भीतर की घटनाओं के बारे में: वे सबसे अधिक संभावना माता-पिता (मास्टर व्यू) में जुड़े रहेंगे, इस दृष्टिकोण के साथ नहीं।


0

मैं निम्नलिखित दृष्टिकोण का उपयोग करना पसंद करता हूं जो बच्चे के विचारों को ठीक से निकालना भी सुनिश्चित करता है। यहाँ Addy Osmani की पुस्तक का एक उदाहरण दिया गया है ।

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

घटनाओं को फिर से सौंपने की कोई जरूरत नहीं है क्योंकि यह महंगा है। निचे देखो:

    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);            
    }
});
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.