一篇关于 ExtJS 的推荐
English version’s here
前言
这是一篇推荐 ExtJS 框架的文章,ExtJS 是我最近来非常喜欢的前端框架,它是由 Sencha 公司开发维护的商业软件,授权比较贵,但是它同样提供了基于 GPL 协议的社区版。虽然通常社区版更新的比较慢,但是你可以使用它的绝大部分功能。
但是自 ExtJS 4.x 版本之后,国内的开发者使用的非常之少,相对的中文资料也非常少。究其原因据我猜测可能是因为授权问题、中文文档差一点、以及不太适合 to-C 应用。而 B 端客户是中国互联网公司不太重视的地方 —— 可能并没有像做 to-C 应用一样有高昂的利润。
为什么我要使用 ExtJS 而不是 Angular / Vue / React
设计
首先,因为 Angular / Vue / React 本身并不提供设计,所以使用它们开发的各种UI框架层出不穷,像是 Ant Design / Material - UI / ElementUI / IView 等等,设计理念越来越趋向同质化。但在 to-B 的应用中,我认为当前的设计语言方向并不太实用,充斥着大量的留白空间、硕大的内外边距、花哨但无用的特效等,让我在做 B 端应用的时候不敢苟同。
反观 ExtJS 一直是非常统一而且不断在进步,当前版本提供了 classic
和 mordern
两个 toolkit,分别适用于桌面环境和移动环境,提供了几乎所有你能用到的 UI 组件,而且设计高度统一, classic toolkit 里面的 theme-classic
主题是永恒的经典,十几年过去仍不显过时 —— 我认为这是所有 ExtJS 官方主题里面为数不多的还算好看的主题了。
开发
诸多前端框架中,我认为 Angular (目前我还没有用它开发过任何实际上线的应用) 和 ExtJS 是最接近后端开发思想的,尤其是 ExtJS,后端人员理解起来相对其他的框架要好一点,可以以更加工程化的代码组织方式来进行开发,这对整个项目的结构是个非常好的事情。在前端越来越碎片化的今天,这可以有效的降低一些学习成本,提高项目的整体质量。
ExtJS 的提供了各种各样的组件、工具包,而且可以非常方便的与扩展和无侵入重写,你几乎不需要使用其他的第三方库就可以工作的非常好,不用担心各种版本问题、冲突问题等等。
文档
ExtJS 的 API 文档非常之详尽,对开发者非常友好,所有的配置、属性、方法、事件等等一目了然。 而且由于所有用到的组件出自自家,文档风格和内容也高度统一。 ExtJS 的文档
其他
然后就该说一下其他框架了,因为 AngularJS 我对 Angular 抱有非常大的成见,可能很多人都不记得这个框架了,它实际可以算是 Angular 的前身,在 Vue / React 还没出现之前它是最酷的前端框架,我曾经开发过一个开源项目 ONES ,它的前端就是基于 AngularJS 开发的,它让我见识了一个完全不一样的前端世界。但是后来 Google 放弃了它,这也是我不再维护这个项目的原因之一,它伤透了我的心。
可能是因为我的技术水平问题,在一个 Vue 2 + ElementUI 的前端中型项目中的表现并不是非常好。而对于React,我感觉唯一的原因就是我不太喜欢an JSX
的写法。
另外目前为止我还没有使用 Angular / React / Vue 做过非常复杂的应用,这可能也是我对他们的理解并没有那么深刻的原因。
用 ExtJS 做过的项目
这是一个比较典型的内部应用,前端涉及到了 ExtJS / Electron / Vue 等等很多技术,使用的是 ExtJS classic toolkit。通常我在做这种项目的时候喜欢做一些通用的视图,比如 Form / Grid / Bill / DetailView / QueryWindow 等等,这样可以有效的减少大量的同质开发工作,而且得益于 ExtJS 的类机制和Mixins机制,他们工作的非常好。 前端代码并不需要写很多,只需要后端定义好 Entity / Vo 就可以自动生成相应的 DataStructure 供前端使用,前端会根据不同的字段类型使用不同的控件,而且前端也可以进行相应的自定义调整。使用 Python 做了代码生成器,这样几乎无需写任何代码就可以完成一个模块基本的 CRUD 操作。
当然后端也有相应的通用控制器来进行请求的处理,通常只需要定义 Entity 和 Vo 的类成员变量就可以了,甚至如果你不需要 Vo 都可以不定义,JPA 会处理数据库结构同步,CommonRestHandler 会负责CRUD / 导入 / 导出 / 处理权限校验等等工作。
我认为这是一个非常好的方法,这有助于减少大量的同质代码,进行了新一层的抽象。
截图分享


一些代码
Ext.define('yas.lib.base.Grid', {
requires: [
"Ext.grid.Panel",
"Ext.grid.filters.Filters"
],
extend: "Ext.grid.Panel",
autoDestroy: false,
columnLines: true,
// enableLocking: true,
// 默认自动加载数据
autoLoad: true,
border: false,
plugins: {
gridfilters: true
},
layout: "fit",
scrollable: true,
deferRowRender: true,
initQueryParams: null,
xtype: "basegrid",
features: [{
ftype: 'summary',
dock: "bottom"
}, {
ftype: 'grouping',
collapsible: false,
groupHeaderTpl: ['<input class="group-checkbox" type="checkbox" /> {columnName}: {name}({children.length})'],
enableNoGroups: true
}],
// enableLocking: true,
// 按钮类型
actionTypes: [
"add", "edit", "delete", "-",
"refresh", "-", "export", "import",
"-", "trash", "restore"
],
additionalActionTypes: [
"->", "query"
],
actionConfigs: {},
scroll :true,
viewConfig:{
stripeRows:true,
enableTextSelection:true,
scrollable: true
},
selModel: {
injectCheckbox: 0,
mode: "SIMPLE",
checkOnly: false,
allowDeselect: true,
type: "checkboxmodel"
},
bbar: {
xtype: 'pagingtoolbar',
displayInfo: true
},
contextMenu: null,
listeners: {
selectionchange: function(selectionModel, items) {
let toolbar = this.down("toolbar"),
selectedLength = items.length;
if(!toolbar) {
return;
}
for(let i in toolbar.items.items) {
let toolbarBtn = toolbar.items.items[i];
// 菜单
if(toolbarBtn.menu && toolbarBtn.menu.items) {
for(let j in toolbarBtn.menu.items.items) {
let menuBtn = toolbarBtn.menu.items.items[j];
if(!menuBtn.multi) {
selectedLength !== 1 ? menuBtn.disable() : menuBtn.enable();
} else {
selectedLength <= 0 ? menuBtn.disable() : menuBtn.enable();
}
}
continue;
}
if(toolbarBtn.multi === undefined) {
continue;
}
if(!toolbarBtn.multi) {
selectedLength !== 1 ? toolbarBtn.disable() : toolbarBtn.enable();
} else {
selectedLength <= 0 ? toolbarBtn.disable() : toolbarBtn.enable();
}
}
let restoreBtn = toolbar.getComponent("restoreButton");
if(restoreBtn) {
this.trashStation && selectedLength > 0 ? restoreBtn.enable() : restoreBtn.disable();
}
},
/**
* 列变化时 保存到本地
* @param ct
* @param column
*/
columnmove: function() {
this.saveLayout();
},
columnresize: function() {
this.saveLayout();
},
columnshow: function() {
this.saveLayout();
},
columnhide: function() {
this.saveLayout();
},
celldblclick: function( table, td, cellIndex, record, tr, rowIndex, e, eOpts) {
try {
let fieldName = table.getHeaderAtIndex(cellIndex).dataIndex;
yas.lib.Plugin.execute("lib.base.grid.cellDblClick", fieldName, record, table, rowIndex, e, this);
} catch(e) {}
}
},
initComponent: function() {
let me = this;
if(this.apiAlias) {
/**
* 当前使用的API Class
* @type {Object}
*/
this.apiCls = this.apiCls || yas.lib.helper.Api.getApi(this.apiAlias);
this.apiCls.component = this;
}
/**
* 顶部工具栏
* @type {*}
*/
if(this.tbar !== false) {
this.tbar = this.tbar || yas.lib.cv.GridActions.getActions(this.apiCls, this.definedTbar);
}
/**
* 当前使用的 Store
* @type {*}
*/
this.store = this.store || Ext.create(
yas.lib.helper.Helper.aliasToFullName(this.apiAlias, "store")
);
this.store.component = this;
// this.store = this.store || Ext.StoreManager.lookup(this.apiAlias);
/**
* 未定义 columns 时尝试自动定义
*/
this.columns = this.columns && this.columns.length > 0 ? this.columns : this.apiCls.getColumnsForGrid({
grid: this
});
/**
* 原始查询条件
*/
if(this.initQueryParams) {
this.getStore().setQueryParams(this.initQueryParams);
}
/**
* 自动高度
*/
// this.autoHeight && this.setHeight(Ext.getCmp("app_center").getHeight());
// this.autoHeight && this.fitContainer();
/**
* 自动载入初始数据
*/
this.autoLoad && this.getStore().load();
this.callParent(arguments);
if(typeof this.afterInitComponent === "function") {
this.afterInitComponent();
}
},
/**
* 布局保存到本地
*/
saveLayout: function() {
let columns = this.getColumns(),
cleared = {
fieldsList: [],
fieldConfig: {}
},
ignoreXtype = ["checkcolumn", "rownumberer"],
gridName = this.gridName || this.apiAlias;
Ext.Array.each(columns, function(column) {
if(ignoreXtype.indexOf(column.xtype) >= 0 || !column.columnName) {
return;
}
cleared.fieldsList.push(column.columnName);
cleared.fieldConfig[column.columnName] = {
width: column.width,
hidden: column.hidden
};
});
yas.lib.cv.SavedLayouts.save(gridName, cleared);
}
});
最后
如果你在开发企业内部应用 / 开源项目 / 或者企业有能力负担相应的授权成本,而且你的应用更偏重于实用性而不是更华丽,那么我认为 ExtJS 是一个非常好的选择,尤其是作为全栈 Web 开发者,你可以尝试去使用它,而且如果你有任何问题,也随时欢迎和我聊聊!
还是那句老话:
所谓语言 / 框架 / 设计模式等都是实现的业务工具和手段,所以选择最适合的那个
相关资源
- 目前 ExtJS 已经更新到了 7.x, 社区版也已经提供了 npm 方式进行安装,这越来越现代化了。
- 如果你需要 ExtJS 的 GPL 版本,请 申请 Extjs 社区版 或通过 非官方的 Github 仓库
- 如果你可以使用 ExtJS 6.x-CE 版本,可以使用 npm i extjs-gpl
- 文档在这里:ExtJS 官方文档 ,当然你可以下载离线版本
This site is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.