在有限的時間中,要學會一個前端框架不容易,也沒有一個框架是萬能的,但總要有個開始,筆者在進幾年一直在 extjs 的世界打滾,已有三四年的經驗,最近負責的專案除了使用 extjs,後端使用 grails,發現好處多多,就開發過程中的經驗與大家分享,從實際的例子還有兩個框架遇到得整合問題一一介紹
程式的寫法百百種,要能夠完成一個特定功能沒有標準答案,但要寫的精簡需要經驗的累積,甚至需要對語言特性要有一定的了解,剛好筆者最近 review 了一段 extjs 程式碼,可以與大家分享。
在開始之前,必須先提到一個觀念,extjs 雖然是 RIA,並且有自己的類別化系統,不過他的類別或物件脫離不了 javascript,因此 javascript 中的物件、函數、this 的特性他也都有。
接著我們來看一段 extjs 類別的程式碼,如下:
這樣的結構可以參考 extjs 官方對於 Ext.define的說明,其中函數所包含的參數 define( className, data, createdFn )
data 的形態就是 Object,所以在下列程式第二個參數所傳入的就是標準的 javascript Object,只是裡面所定義的屬性 extjs 有自己的特定的命名以便辨別作為處理,但不代表你不可以把它當作一般的 Object 來操做,在使用 extjs 時很容易讓使用的人誤解認為他是個特別的存在,其實他也只是 javascript。
有了這樣的認知,我們看看下面的程式碼,裡面有很多段重覆的程式碼,參考註解。
Ext.define('foodprint.view. ', {
extend: 'Ext.toolbar.Toolbar',
alias: 'widget.stdeditortoolbar',
requires: [
'foodprint.view.CreateBtn',
'foodprint.view.DeleteBtn',
'foodprint.view.SaveBtn',
'foodprint.view.ReadBtn',
'foodprint.view.UpdateBtn'
],
itemId: 'stdEditorToolbar',
initComponent: function() {
var me = this;
Ext.applyIf(me, {
items: [
{
xtype: 'createbtn',
listeners: {
click: {
fn: me.onCreateBtnClick,
scope: me
}
}
},
{
xtype: 'deletebtn',
disabled: true,
listeners: {
click: {
fn: me.onDeleteBtnClick,
scope: me
}
}
},
{
xtype: 'savebtn',
disabled: true,
listeners: {
click: {
fn: me.onSaveBtnClick,
scope: me
}
}
},
{
xtype: 'readbtn'
},
{
xtype: 'updatebtn',
disabled: true,
listeners: {
click: {
fn: me.onUpdateBtnClick,
scope: me
}
}
}
]
});
me.callParent(arguments);
},
onCreateBtnClick: function(button, e, eOpts) {
//重覆
var crebtn=btn;
var delbtn=button.ownerCt.down('button[itemId=deleteBtn]');
var savbtn=button.ownerCt.down('button[itemId=saveBtn]');
var updbtn=button.ownerCt.down('button[itemId=updateBtn]');
var redbtn=button.ownerCt.down('button[itemId=readBtn]');
delbtn.setDisabled(true);
savbtn.setDisabled(false);
updbtn.setDisabled(true);
redbtn.setDisabled(true);
},
onDeleteBtnClick: function(button, e, eOpts) {
//重覆
var crebtn=button.ownerCt.down('button[itemId=createBtn]');
var delbtn=button;
var savbtn=button.ownerCt.down('button[itemId=saveBtn]');
var updbtn=button.ownerCt.down('button[itemId=updateBtn]');
var redbtn=button.ownerCt.down('button[itemId=readBtn]');
delbtn.setDisabled(true);
},
onSaveBtnClick: function(button, e, eOpts) {
//重覆
var crebtn=button.ownerCt.down('button[itemId=createBtn]');
var delbtn=button.ownerCt.down('button[itemId=deleteBtn]');
var savbtn=button;
var updbtn=button.ownerCt.down('button[itemId=updateBtn]');
var redbtn=button.ownerCt.down('button[itemId=readBtn]');
savbtn.setDisabled(true);
},
onUpdateBtnClick: function(button, e, eOpts) {
//重覆
var crebtn=button.ownerCt.down('button[itemId=createBtn]');
var delbtn=button.ownerCt.down('button[itemId=deleteBtn]');
var savbtn=button.ownerCt.down('button[itemId=saveBtn]');
var updbtn=button;
var redbtn=button.ownerCt.down('button[itemId=readBtn]');
updbtn.setDisabled(true);
}
});
上面的每一個函式都重覆宣告了 5 個 btn,這樣在維護上是沒有效率的,假設我們需要在加一個按鈕,我們就必須每個函式在加一個宣告,其實還有更好的方式,假設你了解了物件、函式、還有 this 的特性,我們可以將這五個按鈕的參照存在第二個參數 data 下,透過例如:this.updateBtn=component.down('button[itemId=createBtn]');
如下面程式碼中 onStdEditorToolbarRender
,用到的事件 binding 是 extjs Component render 事件 元件實體化後觸動該事件進行處理,只要找到一個這樣只執行一次的進入點,將需要用到的元件存入 this 這個物件,之後我們就不需要重覆 query 物件的參照,也更加快處理速度,雖然這樣的調整可能效果不明顯,但累積起還是很可觀的,特別當你感覺到慢的時候,可能已經遍佈程式碼中,**好習慣要從日常養成**。
Ext.define('foodprint.view.StdEditorToolbar', {
extend: 'Ext.toolbar.Toolbar',
alias: 'widget.stdeditortoolbar',
requires: [
'foodprint.view.CreateBtn',
'foodprint.view.DeleteBtn',
'foodprint.view.SaveBtn',
'foodprint.view.ReadBtn',
'foodprint.view.UpdateBtn'
],
itemId: 'stdEditorToolbar',
initComponent: function() {
var me = this;
Ext.applyIf(me, {
items: [
{
xtype: 'createbtn',
listeners: {
click: {
fn: me.onCreateBtnClick,
scope: me
}
}
},
{
xtype: 'deletebtn',
disabled: true,
listeners: {
click: {
fn: me.onDeleteBtnClick,
scope: me
}
}
},
{
xtype: 'savebtn',
disabled: true,
listeners: {
click: {
fn: me.onSaveBtnClick,
scope: me
}
}
},
{
xtype: 'readbtn'
},
{
xtype: 'updatebtn',
disabled: true,
listeners: {
click: {
fn: me.onUpdateBtnClick,
scope: me
}
}
}
],
listeners: {
render: {
fn: me.onStdEditorToolbarRender,
scope: me
}
}
});
me.callParent(arguments);
},
onCreateBtnClick: function(button, e, eOpts) {
this.delbtn.setDisabled(true);
this.savbtn.setDisabled(false);
this.updbtn.setDisabled(true);
this.redbtn.setDisabled(true);
},
onDeleteBtnClick: function(button, e, eOpts) {
this.delbtn.setDisabled(true);
},
onSaveBtnClick: function(button, e, eOpts) {
this.savbtn.setDisabled(true);
},
onUpdateBtnClick: function(button, e, eOpts) {
this.updbtn.setDisabled(true);
},
onStdEditorToolbarRender: function(component, eOpts) {
this.crebtn=component.down('button[itemId=createBtn]');
this.delbtn=component.down('button[itemId=deleteBtn]');
this.savbtn=component.down('button[itemId=saveBtn]');
this.updbtn=component.down('button[itemId=updateBtn]');
this.redbtn=component.down('button[itemId=readBtn]');
}
});
優化過的程式碼,可以看到物件的 query 只有一次,之後都可以透過 this 來存取,每個函式所執行的都是關鍵的程式碼,沒有多餘的宣告,雖然是很簡單的觀念與應用,如果知道將會受用無窮,不只在 extjs 在一般的 javascript 開發也可以應用。
Ext JS 教學內容由思創軟體提供,共同作者 @lyhcode 與 @smlsun 目前在校園及企業從事 JavaScript(含 Node.js, Ext JS)與 Java(含 Groovy, Grails, Gradle) 教育訓練及顧問工作。