【如何實現backbone元件化開發】 第二篇:優化方案的實現
在上一篇文章中,我們已經分析了Backbone在元件化開發上的不足,以及如何使用打包手段彌補這些不錯。接下來我們來逐步通過一個例子來講解優化的過程
1. 場景假設
假設我們需要使用Backbone編寫這樣一個輸入框元件,它有一個input和一個label組成。當用戶在input中輸入文字,並且觸發blur事件時,輸入的文字會在label中顯示。如下圖所示。
我們還需要一個tab元件,tab元件上有多個tab切換按鈕,點選不同的按鈕能開啟對應的tab頁。每個tab頁中可以巢狀任意元件,例如巢狀上面的輸入框元件。如下圖所示。
2. 專案結構
專案中包含myInput和tab兩個元件。myInput元件中包含5個檔案,我們挨個來看。
2.1 tpl.htm
tpl.htm是元件的模板檔案,例如underscore模板檔案。
2.2 model.js
model.js是Backbone中的Model例項。
2.3 view.js
view.js是Backbone中的view例項,view.js中會引用tpl.htm。
2.4 index.less
元件樣式檔案
2.5 index.js
元件的入口,元件入口會引入index.less、view.js、model.js。
3. 個檔案解析
3.1 tpl.htm
這裡的模板包含一個input元素和用於展示資料的value
<h3><%=title%></h3>
<div>
<strong><%=label%>:</strong></strong><input class="my-input" type="text" name="firstname" placeholder=<%=placeholder%> />
</div>
<div>
<strong ><%=word%>:</strong><%=value%>
</div>
3.2 model.js
model檔案沒有什麼特殊。
var model = Backbone.Model.extend({
defaults: function () {
return {
title: "title",
label: "label",
placeholder: "placeholder",
word: "word",
value: ""
};
},
});
3.3 view.js
這裡的view.js需要通過underscore-template-loader載入模板檔案。通過這個loader實現模板在程式碼打包階段的預編譯。loader的配置詳見第四章。
// 通過underscore-template-loader載入模板檔案
var tpl = require("./tpl.htm")
var view = Backbone.View.extend({
className: 'input-module',
template: tpl,
events: {
// 監聽input元素的chang事件,並修改model中的value值。
'change .my-input': 'valuechange'
},
valuechange: function(event) {
var newValue = event.currentTarget.value;
this.model.set('value', newValue);
},
render: function() {
this.$el.html(this.template(this.model.attributes));
if (this.model.get('value') != '') {
this.$el.find('input').attr('value', this.model.get('value'));
}
return this;
}
});
3.4 index.less
index.less通過less-loader載入到js中,這也就解決了樣式檔案和元件分離的缺點。
.input-module {
font-family: 'Microsoft YaHei';
}
strong {
color: gray;
}
3.5 index.js
index.js通過引入model、view、模板檔案、樣式檔案,把一個元件所有需要的資源整合了起來。並對外提供元件初始化和配置的介面。
所以外部只需要引入元件的入口檔案,並進行配置,就可以載入元件的所有資源。這樣就做到了元件資源的集中管理。
var model = require("./model"),
view = require("./view");
require("./index.less");
function myInput() {
}
// 對外初始化介面
myInput.prototype.init = function(title, label, placeholder, word) {
this._model = new model({
title: title,
label: label,
placeholder: placeholder,
word: word,
value: ""
});
this._view = new view({
model: this._model
});
this._view.render();
}
// 設定元件標題介面
myInput.prototype.setTitle = function(title) {
this._model.set('title', title);
}
// 獲取Dom
myInput.prototype.getView = function() {
return this._view;
}
module.exports = myInput;
4 優化過程的解析
4.1 underscore-template-loader
在view.js中我們通過underscore-template-loader
實現了在程式碼打包階段的模板編譯,這項優化可以節省瀏覽器的編譯模板所需要的時間,從而加快模組的載入。
loader的配置
module: {
loaders: [
{
test: /\.htm$/,
loader: "underscore-template-loader",
query: {
// 這個配置可以在最終渲染的模板中加入註釋,此處使用模板的檔案路徑作為註釋
prependFilenameComment: __dirname,
}
}
]
}
更詳盡的配置可以在github中找到
4.2 less-loader
為了把模組的樣式和模組js打包到同一個元件模組中,我們可以使用less-loader
。less-loader
可以在程式碼打包階段把less檔案轉義為html內聯樣式,並通過js函式將內聯樣式插入到頁面中。less-loader
的配置並不難,可以自行檢視文件。這裡只貼出基本的配置。
module: {
loaders: [
{
test: /\.less$/,
use: [
'style-loader',
{ loader: 'css-loader', options: { importLoaders: 1 } },
{ loader: 'less-loader', options: { strictMath: true, noIeCompat: true } }
]
}
]
}
4.3 整合view與model
當我們引入一個元件時,我們通常希望元件提供一個入口檔案,元件的使用者只要引入入口檔案就可以呼叫元件的所有資源。然而一個Backbone元件通常由view和model構成,這使得元件的引用變成頭疼的事。
所以我們可以提供一個入口檔案,這個入口檔案會引入元件所需要的view、model、樣式檔案等。入口檔案對外提供init
介面對view和model進行初始化,提供setXXX
介面實現對model的修改,提供getView
介面返回元件渲染結果。這樣我們就可以通過如果檔案操作Backbone元件。
Demo請看上一節中的index.js
。
4.4 元件的巢狀
在Backbone中實現元件巢狀的基本思路是先渲染父元件的Dom,然後將子元件的Dom插入到父元件的節點中。因為每個元件都提供getView介面用於返回元件的Dom,所以父元件可以呼叫這個介面嵌入任意元件。
我們可以通過父元件的init介面,把子元件的例項物件傳入父元件。父元件在構建完自身的Dom後掛在子元件。例如:
tab.prototype.add = function(title, content /* 子元件物件 */, isSelect) {
var m = new model({
title: title,
content: content,
isSelect: isSelect
});
this._collection.add(m);
}
元件巢狀中的另一個關鍵點是父子元件的通訊。先來說父元件如何向子元件傳遞訊息,方案父元件呼叫子元件的介面,例如子元件上可以提供元件銷燬介面,重新整理介面等。
而子元件向父元件傳送訊息則可以通過事件的機制,也就是說子元件的入口需要繼承Backbone的Event事件系統,父元件通過監聽子元件發出的訊息實現通訊。