原文地址:http://www.sencha.com/blog/top-10-ext-js-development-practices-to-avoid/
作者:**Sean Lanktree**
Sean is an Ext JS Professional Services Lead at CNX Corporation.
在[CNX](http://www.cnxcorp.com/),盡管大多數的Ext JS開發工作需要從0開始創建新的應用程序,偶爾會有客戶讓我們幫他們解決內部工作上的性能問題、臭蟲和結構性問題。我們以“清潔工”這種角色進行工作已經有很長一段時間了,在我們審查過的應用程序中,我們注意到,有一些共同的不明智的編碼方法經常會出現。基于過去幾年的審查工作,我們列出了十個我們建議的,在Ext JS應用程序中應該避免的開發方法。
### 1. 過多或不必要的組件嵌套
開發人員最常見的錯誤之一是沒理由的嵌套組件。這樣做,會影響性能和也會造成應用程序的不美觀,如爽邊框火意外的布局行為。在下面的示例1A,在面板內只包含了一個Grid。在這種情況下,該面板是不必要的。如示例1B所示,額外的面板可以取消。要記住的是,表單面板、樹面板、標簽面板和Grid面板都是從面板擴展的,隱藏,在使用這些組件的時候,應該特別注意不要的嵌套情況。
~~~
items: [{
xtype : 'panel',
title: ‘My Cool Grid’,
layout: ‘fit’,
items : [{
xtype : 'grid',
store : 'MyStore',
columns : [{...}]
}]
}]
~~~
示例1A? 不好的:面板(panel)是不必要的
~~~
layout: ‘fit’,
items: [{
xtype : 'grid',
title: ‘My Cool Grid’,
store : 'MyStore',
columns : [{...}]
}]
~~~
示例1B 好:Grid已經是面板,因而可以直接在Grid中使用任何面板屬性
### 2. 清理未使用組件失敗造成內存泄漏
許多開發人員不知道為什么他們的應用程序隨著使用時間越長越來越慢。在用戶瀏覽整個應用程序期間清理未使用組件失敗是最大的一個原因。在下面的實例2A中,每次用戶右鍵單擊Grid的行,都會創建一個新的右鍵菜單。如果用戶保持應用程序處于打開狀態并右鍵單擊行上百次,那么,就會有上百個永遠不會被摧毀的右鍵菜單。對于開發人員和用戶來說,應用程序“看上去”顯示是爭取的是因為只有最后一個被創建的菜單能顯示在頁面上,而且與的則是隱藏的。由于沒有創建新菜單并沒有清理舊的,應用程序的內存利用率就會不斷增長,這最終將導致較慢的操作或瀏覽器崩潰。
示例2A就很好,由于右鍵菜單只在Grid初始化時創建一次,并在用戶每次右鍵單擊行時重復使用。不過,如果Grid被銷毀,右鍵菜單一直存在,盡管它不再需要。最好的方式是示例2C,在Grid銷毀的時候,把右鍵菜單也銷毀。
~~~
Ext.define('MyApp.view.MyGrid',{
extend : 'Ext.grid.Panel',
columns : [{...}],
store: ‘MyStore’,
initComponent : function(){
this.callParent(arguments);
this.on({
scope : this,
itemcontextmenu : this.onItemContextMenu
});
},
onItemContextMenu : function(view,rec,item,index,event){
event.stopEvent();
Ext.create('Ext.menu.Menu',{
items : [{
text : 'Do Something'
}]
}).showAt(event.getXY());
}
});
~~~
示例2A 不好:每一次右鍵單擊都會創建菜單,且永遠不會被銷毀
~~~
Ext.define('MyApp.view.MyGrid',{
extend : 'Ext.grid.Panel',
store : 'MyStore',
columns : [{...}],
initComponent : function(){
this.menu = this.buildMenu();
this.callParent(arguments);
this.on({
scope : this,
itemcontextmenu : this.onItemContextMenu
});
},
buildMenu : function(){
return Ext.create('Ext.menu.Menu',{
items : [{
text : 'Do Something'
}]
});
},
onItemContextMenu : function(view,rec,item,index,event){
event.stopEvent();
this.menu.showAt(event.getXY());
}
});
~~~
示例2B 較好:菜單會在Grid創建時被創建,且每次可重用
~~~
Ext.define('MyApp.view.MyGrid',{
extend : 'Ext.grid.Panel',
store : 'MyStore',
columns : [{...}],
initComponent : function(){
this.menu = this.buildMenu();
this.callParent(arguments);
this.on({
scope : this,
itemcontextmenu : this.onItemContextMenu
});
},
buildMenu : function(){
return Ext.create('Ext.menu.Menu',{
items : [{
text : 'Do Something'
}]
});
},
onDestroy : function(){
this.menu.destroy();
this.callParent(arguments);
},
onItemContextMenu : function(view,rec,item,index,event){
event.stopEvent();
this.menu.showAt(event.getXY());
}
});
~~~
示例2C 最好:Grid被銷毀時,右鍵菜單也被銷毀
### 3.怪物控制器
當看到應用程序擁有一個上千行代碼的超級控制器的時候,我們不知道震驚了多少次。我們更傾向于根據應用程序功能拆分控制器。例如,訂單處理應用程序可能會有劃分條目、出貨量、客戶查找等控制器。這將使導航和維護代碼更容易。
一些開發人員喜歡根據視圖拆分控制器。例如,如果一個應用程序有一個Grid和表單,這將會使用一個控制器來管理Grid和使用一個控制器來管理表單。并沒有一個“正確”的方式來劃分控制器邏輯,只要是一致的就行。要記住,控制器可以與其他控制器進行通信。在示例3A,可以看到如何檢索其他的控制器并調用其中的方法。
~~~
this.getController('SomeOtherController').runSomeFunction(myParm);
~~~
示例3A 獲取另一個控制器的引用并調用它的方法。
作為替代,也可以觸發任何控制器可以監聽到的應用程序層事件。在示例3B和3C,可以看到如何在一個控制器觸發一個應用程序層事件并在另外一個控制器監聽它。
~~~
MyApp.getApplication().fireEvent('myevent');
~~~
示例3B 觸發一個應用程序層事件
~~~
MyApp.getApplication().on({
myevent : doSomething
});
~~~
示例3C 在另一個控制器監聽應用程序層事件
注意: 自從Ext JS 4.2開始,使用多個控制器變得更容易了——他們可以觸發其他控制器可以直接監聽的事件
### 4.源代碼的文件夾結構差
這雖然不影響性能和操作,但會讓找跟進應用程序結構變得困難。隨著應用程序的增長,如果源代碼很有組織,那么尋找源代碼以及增加特性或功能就會很容易。我們常常看到許多開發人員會將所有視圖(即使是很大的應用程序)如示例4A一樣放在一個目錄。我們建議如示例4B所示通過邏輯功能來組織視圖。

示例4A 不好:所有視圖都處于同樣層次

示例4B 好:視圖根據邏輯功能進行組織
### 5.全局變量的使用
即使全局變量是不好的已經廣為人知,但仍然會在我們審查過的一些應用程序中看到他們的身影。使用全局變量的應用程序可能會有名稱沖突等重大問題,并且很難去調試。不使用全局變量,可以在類中定義“屬性”,并引用這些屬性的getter和setter。
例如,假設應用程序需要記錄最后選擇的客戶。一般情況下會如示例5A那樣在應用程序中定義一個變量,這很容易且值可以很便利的被應用程序的其他部分使用。
~~~
myLastCustomer = 123456;
~~~
示例5A 不好:創建全局變量來存儲最后的客戶編號
作為替代,好的做法是創建一個用來保存屬性的類來代替全局變量。在當前情況下,可以創建一個名為Runtime.js來保存應用程序中要使用到的運行屬性。示例5B顯示了Runtime.js在源代碼結構中的位置。

示例5B Runtime.js文件的位置
示例5C顯示了Runtime.js文件的內容,而示例5D顯示了如何在app.js中“請求(require)”它。在應用程序中,就可以如5E或5F那樣在任何地方“設置(set)”或“獲取(get)”屬性。
~~~
Ext.define(‘MyApp.config.Runtime’,{
singleton : true,
config : {
myLastCustomer : 0 // initialize to 0
},
constructor : function(config){
this.initConfig(config);
}
});
~~~
示例5C 用來為應用程序保存全局屬性的Runtime.js文件示例
~~~
Ext.application({
name : ‘MyApp’,
requires : [‘MyApp.config.Runtime’],
...
});
~~~
示例5D 在app.js文件中請求Runtime類
~~~
MyApp.config.setMyLastCustomer(12345);
~~~
示例5E 設置最后客戶的方式
~~~
MyApp.config.getMyLastCustomer();
~~~
示例5F 獲取最后客戶的方式
### 6. id的使用
我們不建議在組件上使用id,因為每一個id必須是唯一的。而這樣很容易導致不小時使用了相同的id,從而引起重復的DOM id(名稱沖突)。相反,應該讓框架來處理id的生成。使用Ext JS的組件查詢,沒有任何理由要為Ext JS組件指定一個id。示例6A顯示了一個應用程序中的兩段代碼,在這里創建了兩個不同的保存按鈕,而這兩個保存按鈕的id都為“savebutton”,從而導致了名稱沖突。顯而易見,在下面的代碼很容易找出名稱沖突,但在一個大型應用程序中,要確定名稱沖突會很困難。
~~~
// here we define the first save button
xtype : 'toolbar',
items : [{
text : ‘Save Picture’,
id : 'savebutton'
}]
// somewhere else in the code we have another component with an id of ‘savebutton’
xtype : 'toolbar',
items : [{
text : ‘Save Order’,
id : 'savebutton'
}]
~~~
示例6A 不好:為組件分配了重復id將造成名稱沖突
替代方法是,如果需要手動確定每一個組件的,可以如示例6B那樣使用itemid代替id。這就可以解決命名沖突,并且仍然可以通過itemid來引用組件。通過itemid有許多方法來獲取組件的引用。示例6C列出了部分方法。
~~~
xtype : 'toolbar',
itemId : ‘picturetoolbar’,
items : [{
text : 'Save Picture',
itemId : 'savebutton'
}]
// somewhere else in the code we have another component with an itemId of ‘savebutton’
xtype : 'toolbar',
itemId: ‘ordertoolbar’,
items : [{
text : ‘Save Order’,
itemId: ‘savebutton’
}]
~~~
示例6B 好:使用itemid來創建組件
~~~
var pictureSaveButton = Ext.ComponentQuery.query('#picturetoolbar > #savebutton')[0];
var orderSaveButton = Ext.ComponentQuery.query('#ordertoolbar > #savebutton')[0];
// assuming we have a reference to the “picturetoolbar” as picToolbar
picToolbar.down(‘#savebutton’);
~~~
示例6C 好:使用itemid引用組件
### 7.不可靠的組件引用
有時候會看到代碼利用組件位置來引用組件。應該避免出現這樣的情況,因為有任何條目被增加、移除或嵌入不同的組件,就會導致錯誤。示例7A顯示了幾種常見情況。
~~~
var mySaveButton = myToolbar.items.getAt(2);
var myWindow = myToolbar.ownerCt;
~~~
示例7A 不好:避免基于組件位置來獲取組件引用
替代方法是,如示例7B那樣使用組件查詢或者最佳的up或down方法來返回引用。使用這種技術,代碼就很少會因以后的結構或組件位置變化而導致錯誤。
~~~
var mySaveButton = myToolbar.down(‘#savebutton’); // searching against itemId
var myWindow = myToolbar.up(‘window’);
~~~
示例7B? 好:使用組件查詢來返回相關引用
### 8. 不遵守大寫/小寫命名約定
Sencha在為組件、屬性或xtype等等命名的時候,會遵循某些大寫/小寫標準。為了避免混淆,保持代碼清潔,應對遵循相同的標準。示例8A顯示幾個不正確的情形。示例8B顯示了在相同情況下,使用正確的大寫/小寫命名約定的情形。
~~~
Ext.define(‘MyApp.view.customerlist’,{ // should be capitalized and then camelCase
extend : ‘Ext.grid.Panel’,
alias : ‘widget.Customerlist’, // should be lowercase
MyCustomConfig : ‘xyz’, // should be camelCase
initComponent : function(){
Ext.apply(this,{
store : ‘Customers’,
….
});
this.callParent(arguments);
}
});
~~~
示例8A 粗體顯示的地方為不正確的大寫/小寫命名
~~~
Ext.define(‘MyApp.view.CustomerList’,{
extend : ‘Ext.grid.Panel’,
alias : ‘widget.customerlist’,
myCustomConfig : ‘xyz’,
initComponent : function(){
Ext.apply(this,{
store : ‘Customers’,
….
});
this.callParent(arguments);
}
});
~~~
示例8B 粗體顯示的地方為正確的大寫/小寫命名
另外,如果觸發任何自定義事件,事件的名稱應對是小寫的。當然,不遵循這些約定,一切都仍然會工作,但為什么要迷失在標準之外并編寫不太干凈的代碼?
### 9. 將組件約束在父組件的布局
在示例9A,面板總是會有“region:center”屬性,因此,當想重用它的時候就可能行不通,例如將它放到“west”區域。
~~~
Ext.define('MyApp.view.MyGrid',{
extend : 'Ext.grid.Panel',
initComponent : function(){
Ext.apply(this,{
store : ‘MyStore’,
region : 'center',
......
});
this.callParent(arguments);
}
});
~~~
示例9A 壞:“center”區域不應該放在這里
代替方法是,如示例8B那樣,在創建組件的時候才指定布局配置。這樣,就可以將組件重用到任何你希望的地方,切不受布局配置的約束。
~~~
Ext.define('MyApp.view.MyGrid',{
extend : 'Ext.grid.Panel',
initComponent : function(){
Ext.apply(this,{
store : ‘MyStore’,
......
});
}
});
// specify the region when the component is created...
Ext.create('MyApp.view.MyGrid',{
region : 'center'
});
~~~
示例9B 好:在創建組件的時候才知道區域
如示例9C所示,也可以為組件提供一個默認的區域,如果需要,可以重寫它。
~~~
Ext.define('MyApp.view.MyGrid',{
extend : 'Ext.grid.Panel',
region : 'center', // default region
initComponent : function(){
Ext.apply(this,{
store : ‘MyStore’,
......
});
}
});
Ext.create(‘MyApp.view.MyGrid’,{
region : ‘north’, // overridden region
height : 400
});
~~~
示例9C 也很好:指定默認區域,在需要的時候重寫
### 10. 使代碼比所需的更復雜
有很多時候,我們會看到比所需更復雜的代碼。這通常是由于對每個組件的可用方法不熟悉造成的。最常見的一種情況是,為每一個表單字段單獨從數據記錄中加載數據。示例10A就顯示這種情況。
~~~
// suppose the following fields exist within a form
items : [{
fieldLabel : ‘User’,
itemId : ‘username’
},{
fieldLabel : ‘Email’,
itemId : ‘email’
},{
fieldLabel : ‘Home Address’,
itemId : ‘address’
}];
// you could load the values from a record into each form field individually
myForm.down(‘#username’).setValue(record.get(‘UserName’));
myForm.down(‘#email’).setValue(record.get(‘Email’));
myForm.down(‘#address’).setValue(record.get(‘Address’));
~~~
示例10A 不好:為表單字段單獨的從記錄中加載數據
替代單獨加載每一個值的方法是,使用loadRecord方法從記錄中為所有字段的數據到表單字段,這只需要一行代碼。如示例10B所示,這里的關鍵是確保表單字段的name屬性和記錄的字段名稱是一樣的。
~~~
items : [{
fieldLabel : ‘User’,
name : ‘UserName’
},{
fieldLabel : ‘Email’,
name : ‘Email’
},{
fieldLabel : ‘Home Address’,
name : ‘Address’
}];
myForm.loadRecord(record);
~~~
示例10B 好:使用loadRecord方法只需要一行代碼就可加載所有表單字段
這只是比必需的更復雜的代碼的其中一個例子。而當中的重點是要審視組件的所有方法和和示例,以確保正在使用簡單和適當的技術。
[CNX公司](http://www.cnxcorp.com/)是Sencha認證選擇合作伙伴。Sencha合作伙伴網絡是Sencha專業服務器團隊的寶貴擴展。
自從1996年以來,CNX一直處于自定義商業應用程序開發的先行者。在2008年,CNX在Ext JS上對它的基于瀏覽器的用戶界面開發進行了標準化,在2010年,又添加了Sencha Touch作為移動開發的標準。我們已經在世界各地,為教育、金融、食品、法律、物流、制造、出版和零售等許多行業的客戶創建了一流的Web應用程序。我們的開發團隊以芝加哥市中心的公司辦公室為基地,可以處理任何規模的項目。CNX可以獨立工作或與您的團隊一起,以快速、經濟高效的方式去實現項目目標。請查閱我們的網站http://www.cnxcorp.com。
- 前言
- extjs 4 tree 的text不顯示
- 窗口顯示時讓字段獲得焦點
- 如何編寫一個使用Store更新復選框的CheckboxGroup的插件
- 如何了解事件中回調函數的參數
- 很多人需要的,帶時間的日期選擇器
- 一個網上找到的,在Grid中嵌套Grid的示例:Nested Grids Example
- 修改Ext.ux.GroupTabPanel讓它支持延遲渲染
- 初學者比較容易犯的布局錯誤(手風琴布局)
- Ext JS添加子組件的誤區
- 使用Ext JS,不要使用頁面做組件重用,盡量不要做頁面跳轉
- 【翻譯】十大要避免的Ext JS開發方法
- 一個不錯的擴展:Ext.ux.container.ButtonSegment
- 在VS2012中實現Ext JS的智能提示太簡單了
- 為什么要使用“var me=this”這樣的寫法
- 一個很不錯的支持Ext JS 4的上傳按鈕
- 【翻譯】熱門支持小提示:2013年12月
- 【翻譯】在Ext JS應用程序中使用自定義圖標
- 演練Ext JS 4.2自定義主題
- 【翻譯】培訓提示:解決常見編碼問題的簡單技巧
- 【翻譯】從Store生成Checkbox Group
- 【翻譯】將Ext JS Grid轉換為Excel表格
- 【翻譯】Ext JS 5:為不同設備設置不同的主題