javascript - 如何在 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);來緩解,但在第一次,以及最常見的情況下,你會不必要地做更多工作。透過附加子元素,你迫使渲染函數必須了解父元素的 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,將會創建一個全新的子視圖,並且其所有當前狀態都會丟失。這看起來也可能對記憶體洩漏造成問題。
真的很好奇想知道你們的看法。你會使用哪種情境?還是有第四種神奇的方法能解決所有這些問題?
你有沒有追蹤過一個 View 的渲染狀態?比如說一個 renderedBefore 標誌?看起來真的很蹩腳。
答案#1
這是一個很好的問題。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 個子視圖,InfoView 和 PhoneListView,以及一些額外的 div,其中一個 #name 需要在某個時候設置。PhoneListView 自身也包含子視圖,是一個 PhoneView 條目的陣列。
那麼回到你的實際問題。我根據視圖類型不同來處理初始化和渲染。我把我的視圖分成兩種類型,Parent 視圖和 Child 視圖。
它們之間的區別很簡單,Parent 視圖包含子視圖,而 Child 視圖則不包含。因此在我的例子中,ParentView 和 PhoneListView 是 Parent 視圖,而 InfoView 和 PhoneView 條目則是 Child 視圖。
正如我之前提到的,這兩個類別之間最大的差異在於它們被允許渲染的時機。在理想的情況下,我希望 Parent 視圖只渲染一次。任何模型變化時的重新渲染應由它們的子視圖來處理。另一方面,Child 視圖,我允許它們在需要時隨時重新渲染,因為沒有其他視圖依賴它們。
更詳細一點,對於 Parent 視圖,我希望我的 initialize 函數做幾件事情:
- 初始化我自己的視圖
- 呈現我自己的觀點
- 創建並初始化任何子視圖。
- 為我的每個子視圖分配一個元素(例如,
InfoView將被分配#info)。
步驟 1 非常容易理解。
第二步,渲染,這樣做是為了確保子視圖依賴的任何元素在我嘗試分配它們之前已經存在。通過這樣做,我知道所有的子 events 都會正確設置,並且我可以隨意重新渲染它們的區塊,而不必擔心需要重新委派任何東西。我實際上並不在這裡 render 任何子視圖,我允許它們在自己的 initialization 中進行渲染。
步驟 3 和 4 其實是在我傳入 el 建立子視圖時同時處理的。我喜歡在這裡傳入一個元素,因為我覺得父視圖應該決定子視圖可以將內容放在哪裡。
在渲染時,我試著保持對 Parent 視圖的操作相當簡單。我希望 render 函數只做渲染父視圖的事。不進行事件委派,不渲染子視圖,什麼都不做。只是簡單地渲染而已。
不過有時這並不總是有效。例如在我上面的例子中,#name 元素每當模型中的名稱改變時都需要更新。然而,這個區塊是 ParentView 模板的一部分,並不是由專門的 Child 視圖處理,所以我採用變通方法。我會創建某種 subRender 函數,只 替換 #name 元素的內容,而不必整個 #parent 元素都重建。這可能看起來像是權宜之計,但我真的發現這比必須擔心重繪整個 DOM 並重新附加元素等操作更有效。如果我真的想要讓它乾淨些,我會創建一個新的 Child 視圖(類似於 InfoView),來處理 #name 區塊。
現在對於 Child 視圖,initialization 與 Parent 視圖非常相似,只是沒有創建任何進一步的 Child 視圖。所以:
- 初始化我的視圖
- Setup 綁定以監聽我關心的模型的任何變更
- 渲染我的視圖
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,你只需要在 initialization 和 render 函數中加入更多邏輯來處理集合。你如何處理集合完全取決於你自己,但至少你需要監聽集合事件並決定如何渲染(附加/移除,或者只是重新渲染整個區塊)。我個人喜歡附加新視圖並移除舊的,而不是重新渲染整個視圖。
PhoneView 將幾乎與 InfoView 相同,只是只監聽它關心的模型變化。
希望這對你有一點幫助,如果有任何讓你困惑或不夠詳細的地方,請告訴我。
答案#2
我不確定這是否能直接回答你的問題,但我認為這是相關的:
http://lostechies.com/derickbailey/2011/10/11/backbone-js-獲取被點擊元素的模型/
我撰寫這篇文章的背景當然不同,但我認為我提供的兩個解決方案,以及各自的優缺點,應該能讓你朝正確的方向前進。
