一篇关于 ExtJS 的推荐

laofahai laofahai
2022年5月6日
Development
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 一直是非常统一而且不断在进步,当前版本提供了 classicmordern 两个 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 / 导入 / 导出 / 处理权限校验等等工作。

我认为这是一个非常好的方法,这有助于减少大量的同质代码,进行了新一层的抽象。

截图分享

jones screenshot jones screenshot 2

一些代码

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 开发者,你可以尝试去使用它,而且如果你有任何问题,也随时欢迎和我聊聊!

还是那句老话:

所谓语言 / 框架 / 设计模式等都是实现的业务工具和手段,所以选择最适合的那个

相关资源

本站所有内容采用知识共享署名-非商业性使用-相同方式共享 4.0协议发布

评论