簡體   English   中英

如何在Backbone.js中處理初始化和渲染子視圖?

[英]How to handle initializing and rendering subviews in Backbone.js?

我有三種不同的方法來初始化和呈現視圖及其子視圖,並且每個方法都有不同的問題。 我很想知道是否有更好的方法可以解決所有問題:


情景一:

在父級的初始化函數中初始化子級。 這樣,並不是所有東西都會陷入渲染狀態,因此渲染時阻塞較少。

initialize : function () {

    //parent init stuff

    this.child = new Child();
},

render : function () {

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

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

問題:

  • 最大的問題是第二次在父級上調用render將刪除所有的子事件綁定。 (這是因為jQuery的$.html()工作原理。)這可以通過調用this.child.delegateEvents().render().appendTo(this.$el);來緩解this.child.delegateEvents().render().appendTo(this.$el); 相反,但是第一個,也是最常見的情況,你正在做更多不必要的工作。

  • 通過附加子項,可以強制render函數了解父DOM結構,以便獲得所需的順序。 這意味着更改模板可能需要更新視圖的渲染功能。


情景二:

初始化父級的initialize()中的子節點,但不是追加,而是使用setElement().delegateEvents()將子節點設置為父模板中的元素。

initialize : function () {

    //parent init stuff

    this.child = new Child();
},

render : function () {

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

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

問題:

  • 這使得delegateEvents()現在變得必要了,這只是在第一個場景中的后續調用中需要的一點點消極。

情景三:

而是在父級的render()方法中初始化子級。

initialize : function () {

    //parent init stuff
},

render : function () {

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

    this.child = new Child();

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

問題:

  • 這意味着現在必須將渲染函數與所有初始化邏輯聯系起來。

  • 如果我編輯其中一個子視圖的狀態,然后在父視圖上調用render,將創建一個全新的子節點,並且它的所有當前狀態都將丟失。 對於內存泄漏而言,它似乎也會變得冒險。


非常好奇讓你的家伙接受這個。 你會使用哪種場景? 還是有第四個神奇的解決所有這些問題?

你有沒有跟蹤視圖的渲染狀態? 說一個renderedBefore標志? 看起來真的很笨拙。

這是一個很好的問題。 Backbone很棒,因為它缺乏假設,但它確實意味着你必須(決定如何)自己實現這樣的事情。 在查看了我自己的東西之后,我發現我(有點)使用了場景1和場景2的混合。我不認為存在第四個神奇場景,因為,簡單地說,你在場景1和場景2中所做的一切都必須是完成。

我認為用一個例子解釋我喜歡用它來解釋它是最容易的。 假設我將這個簡單的頁面分解為指定的視圖:

分頁

假設HTML在呈現后是這樣的:

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

希望HTML與圖表的匹配非常明顯。

ParentView包含2個子視圖, InfoViewPhoneListView以及一些額外的div,其中一個#name需要在某個時刻設置。 PhoneListView擁有自己的子視圖,一組PhoneView條目。

那么關於你的實際問題。 我根據視圖類型處理初始化和渲染。 我將我的觀點分為兩種類型, Parent視圖和Child視圖。

它們之間的區別很簡單, Parent視圖保存子視圖,而Child視圖不保存。 所以在我的例子中, ParentViewPhoneListViewParent視圖,而InfoViewPhoneView條目是Child視圖。

就像我之前提到的,這兩個類別之間的最大區別在於它們被允許渲染。 在一個完美的世界中,我希望Parent視圖只能渲染一次。 當模型發生變化時,由子視圖決定是否處理任何重新渲染。 另一方面, Child視圖允許在他們需要的任何時候重新渲染,因為他們沒有依賴它們的任何其他視圖。

更詳細一點,對於Parent視圖,我喜歡我的initialize函數來做一些事情:

  1. 初始化我自己的觀點
  2. 渲染我自己的視圖
  3. 創建並初始化任何子視圖。
  4. 在視圖中為每個子視圖分配一個元素(例如, InfoView將被分配#info )。

第1步非常自我解釋。

第2步,完成渲染,以便在我嘗試分配子視圖之前,子視圖所依賴的任何元素都已存在。 通過這樣做,我知道所有子events都將被正確設置,我可以根據需要重新渲染它們的塊,而不必擔心必須重新委托任何事情。 我實際上並沒有在這里render任何子視圖,我允許他們在自己的initialization

第3步和第4步實際上是在創建子視圖時傳入el的同時處理的。 我喜歡在這里傳遞一個元素,因為我覺得父母應該在自己的視圖中確定允許孩子放置其內容的位置。

對於渲染,我試圖讓它對於Parent視圖非常簡單。 我希望render函數除了渲染父視圖之外什么都不做。 沒有事件委托,沒有渲染子視圖,沒有。 只是一個簡單的渲染。

有時這並不總是有效。 例如,在上面的示例中,只要模型中的名稱發生更改,就需要更新#name元素。 但是,此塊是ParentView模板的一部分,而不是由專用的Child視圖處理,所以我解決了這個問題。 我將創建某種subRender函數, 它只替換#name元素的內容,而不必#parent整個#parent元素。 這可能看起來像是一個黑客,但我真的發現它比不必擔心重新渲染整個DOM和重新附加元素等更好。 如果我真的想讓它干凈,我會創建一個新的Child視圖(類似於InfoView )來處理#name塊。

現在對於Child視圖, initialization非常類似於Parent視圖,只是沒有創建任何進一步的Child視圖。 所以:

  1. 初始化我的觀點
  2. 安裝程序綁定偵聽我關心的模型的任何更改
  3. 渲染我的觀點

Child視圖渲染也很簡單,只需渲染並設置我的el的內容。 再一次,沒有搞亂代表團或類似的東西。

以下是我的ParentView可能的示例代碼:

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

你可以在這里看到我對subRender實現。 通過將更改綁定到subRender而不是render ,我不必擔心爆破並重建整個塊。

這是InfoView塊的示例代碼:

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

綁定是這里的重要部分。 通過綁定到我的模型,我永遠不必擔心自己手動調用render 如果模型更改,此塊將重新呈現自身而不會影響任何其他視圖。

PhoneListView將類似於ParentView ,您只需要在initializationrender函數中使用更多邏輯來處理集合。 你如何處理集合真的取決於你,但你至少需要聽取集合事件並決定你想要渲染的方式(追加/刪除,或者只是重新渲染整個塊)。 我個人喜歡添加新視圖並刪除舊視圖,而不是重新渲染整個視圖。

PhoneView幾乎與InfoView完全相同,只是聽取它關心的模型更改。

希望這有點幫助,如果有什么令人困惑或不夠詳細,請告訴我。

我不確定這是否直接回答了你的問題,但我認為這是相關的:

http://lostechies.com/derickbailey/2011/10/11/backbone-js-getting-the-model-for-a-clicked-element/

當然,我設置這篇文章的背景是不同的,但我認為我提供的兩個解決方案,以及每個解決方案的優缺點,都應該讓你朝着正確的方向前進。

對我而言,通過某種標志來區分視圖的初始設置和后續設置似乎不是世界上最糟糕的想法。 為了使這個干凈簡單,應該將標志添加到您自己的View中,該View應該擴展Backbone(Base)View。

和Derick一樣我不完全確定這是否能直接回答你的問題,但我認為在這方面可能至少值得一提。

另請參閱:在Backbone中使用Eventbus

Kevin Peel給出了一個很好的答案 - 這是我的tl; dr版本:

initialize : function () {

    //parent init stuff

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

    this.child = new Child();
},

我所做的是給每個孩子一個身份(Backbone已經為你做了這個:cid)

當Container執行渲染時,使用'cid'和'tagName'為每個子節點生成占位符,因此在模板中,子節點不知道Container將放置它的位置。

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

比你可以使用

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

不需要指定的占位符,Container只生成占位符而不是子項的DOM結構。 Cotainer和Children仍然只生成一次DOM元素。

我試圖避免這些視圖之間的耦合。 我通常有兩種方式:

使用路由器

基本上,您讓路由器功能初始化父視圖和子視圖。 因此視圖彼此不了解,但路由器處理所有這些。

將相同的el傳遞給兩個視圖

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

兩者都知道相同的DOM,您可以隨意訂購它們。

這是一個用於創建和渲染子視圖的輕量級混合,我認為這解決了該線程中的所有問題:

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

此插件采用的方法是在一次呈現父視圖后創建和渲染子視圖。 然后,在后續渲染父視圖時,$ .detach子視圖元素,重新渲染父元素,然后將子視圖元素插入適當的位置並重新渲染它們。 這樣子視圖對象可以在后續渲染中重用,並且不需要重新委派事件。

請注意,集合視圖的情況(集合中的每個模型都用一個子視圖表示)是完全不同的,我認為它值得討論/解決。 我知道的最佳通用解決方案是Marionette中CollectionView

編輯:對於集合視圖案例,如果您需要根據點擊選擇模型和/或拖放進行重新排序,您可能還想查看這個更多以UI為重點的實現

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM