资讯动态

ExtJS组件化框架实战:从创建组件到布局通信与封装复用

发布时间:2026/10/9 17:30:49 来源:尧图企业网站定制
ExtJS 很早就被贴上了重、老、贵的标签很多人一听到它就直接劝退。但如果你真的花时间用过它的组件体系会有一个完全不同的感受这根本不是一袋子散装控件而是一套纪律严明的组件化框架。刚开始被那一堆类名和配置项吓到很正常我当年入门时也是靠先抄一个能跑的界面再去理解每条配置到底在控制什么才上手的。这篇文章我想带你按组件这条主线走一遍怎么创建组件、有哪些高频组件值得记住、布局系统怎么把组件摆得井井有条、组件之间怎么通信以及怎么把重复的页面沉淀成自己的组件库。适合刚接触 ExtJS 的前端也适合做中后台系统时想找个成熟方案的全栈工程师。1. 先搞清楚 ExtJS 的组件体系到底是什么1.1 从控件到容器组件之间的继承关系没那么神秘ExtJS 里几乎一切可见的 UI 都是组件但它不是按钮是按钮、表格是表格的平铺结构而是一棵严格的继承树。最底层的基类是Ext.Component它定义了所有组件的通用能力尺寸、位置、隐藏显示、启用禁用、事件绑定、渲染流程。再往下是Ext.container.Container它在组件基础上增加了装子组件的能力可以管理items、布局layout、默认配置defaults。真正日常打交道的大多是Ext.panel.Panel以及它的子类比如Ext.form.Panel、Ext.grid.Panel、Ext.tree.Panel。把这层关系记住非常值钱因为后面遇到任何 API你都能在脑子里快速定位这是组件通用属性还是容器专属属性还是某个子类才有的能力。比如renderTo是组件层面的items是容器层面的region是 Border 布局下面板才需要的配置。ExtJS 中的典型继承关系大致是这样类名职责常用场景Ext.Component单一可见元素无 items按钮、字段、工具栏按钮Ext.container.Container装载子组件管理布局作为盒子存在的各种容器Ext.panel.Panel带标题、工具栏、底部按钮的面板页面区块、窗口内容Ext.form.Panel容纳表单字段并统一提交登录、查询、编辑表单Ext.grid.Panel表格展示与编辑数据列表管理、报表Ext.tree.Panel树形数据展示目录、组织架构、菜单Ext.window.Window独立浮层窗口弹窗、详情、对话框这张表不是让你背诵而是让你形成一种判断习惯想清楚我要做的东西本质上是个什么角色再去选类。1.2 生命周期组件的渲染和销毁是有顺序的组件不是一拍脑袋就能直接操作 DOM 的。它的出生和成长有一条固定的流水线先由配置对象构造实例然后进入initComponent初始化内部结构再渲染出 DOM渲染完成触发afterrender之后经过布局layout调整位置尺寸。销毁时还会走beforeDestroy和destroy。这个顺序直接影响你写代码的方式。如果你在initComponent里就去操作 DOM 元素大概率拿到null因为这时候组件还没渲染完。正确的做法是初始化阶段只管配置、创建子组件、绑定数据操作 DOM 的事等afterrender事件再干。重写initComponent时必须记得调用this.callParent()否则父类的初始化逻辑会直接断掉这是新手最容易踩的坑之一。2. 上手第一步用 xtype 搭出第一个界面2.1 创建组件的三种姿势Ext.create、Ext.widget 与 xtype创建组件最直白的写法是Ext.create(Ext.panel.Panel, {...})这也是很多教程里的示范写法。但真正写项目时你更多会看到xtype这种声明式写法。xtype 相当于给组件类起了一个短名字比如panel对应Ext.panel.Panel、grid对应Ext.grid.Panel、textfield对应Ext.form.field.Text。在容器的items里写{ xtype: panel, title: 测试 }时组件并没有立刻被创建而是由容器在渲染阶段按配置实例化。这个延迟实例化机制很关键它让页面结构可以先以一份普通对象描述出来组件孩子在自己需要渲染时才诞生也方便框架在创建时统一注入布局和父容器引用。三者用起来的感觉写法适用场景Ext.create(className, config)需要独立管理组件实例手动引用它Ext.widget(xtype, config)只记得 xtype 名不想写全类名items中的xtype配置声明式嵌套组件交给容器管理实际开发中我绝大多数时候都走第三条路只有弹窗、动态加载这类需要手动挂到页面的组件才用前两种。2.2 从 Panel 到 Viewport搭一个像样的后台页面骨架学会用容器组装页面后就能快速做出一个后台管理系统的壳子。经典的写法是用一个Ext.container.Viewport作为全屏根容器它自动撑满浏览器视口不需要设置宽高。外层用layout: border东西南北中五个区域各司其职顶部放系统标题左侧放菜单树中间放 TabPanel 承载各个业务页底部放版权信息。大概是这样一段代码Ext.onReady(function () { Ext.create(Ext.container.Viewport, { layout: border, items: [{ xtype: panel, region: north, height: 56, title: 管理系统头部 }, { xtype: treepanel, region: west, width: 220, title: 功能菜单, rootVisible: false, store: menuStore }, { xtype: tabpanel, region: center, items: [{ title: 订单管理, layout: fit, items: [{ xtype: grid, store: orderStore }] }] }] }); });这段代码表面上只是声明了几个组件实际已经完成了组件树的建立。你能看出Viewport 是根west 是树center 是页签页签里面再嵌套一个 grid这样的层级。调试阶段想确认组件树长什么样可以在浏览器控制台执行Ext.ComponentQuery.query(grid)能打印出页面上所有 grid 实例也可以用它定位某个具体组件。2.3 看懂配置项每个属性背后都是一组 getter/setterExtJS 组件有一个特点几乎所有配置项是带感应的——配置width得到getWidth()和setWidth()配置title得到setTitle()。所以你在 API 文档里看到的 config 列表就是组件对外暴露的控制面板。记住这个规律很多方法你不需要背看配置项名字就能猜出来。有个实用技巧动态修改组件状态时尽量用 setter 而不是直接改属性。比如panel.setTitle(新标题)内部会触发更新逻辑而panel.title 新标题可能只是改了个变量界面纹丝不动。这类问题非常隐蔽排错时可以先怀疑一下是不是没用 setter。3. 高频组件逐个摸一遍表单、表格、树、窗口3.1 Form 组件校验、联动和提交是三个核心场景表单类是后台系统里最常打交道的东西。Ext.form.Panel里可以放各种字段组件textfield、numberfield、combobox、datefield、textarea、checkbox等。每个字段通过name属性对应提交给后端的参数名。校验是表单里最容易做乱的地方。简单的必填可以靠allowBlank: false格式校验可以用regex正则也可以借助 ExtJS 自带的vtype比如vtype: email、vtype: url。常见的联动逻辑比如选了省份后城市下拉框刷新可以在省份字段的listeners里监听change事件然后手动去更新城市字段的 store。提交方面传统方式是form.submit({ url: xxx })内部走 Ajax也可以用form.getValues()自己拼参数交给项目里的统一请求层。我的建议是除非完全在用 ExtJS 的 Ajax 体系否则尽量只拿表单组件负责收集和校验提交逻辑交给自己的封装后续接任意后端都方便。buttons: [{ text: 保存, handler: function () { var form this.up(form).getForm(); if (form.isValid()) { form.submit({ url: /api/save, success: function () { Ext.Msg.alert(提示, 保存成功); }, failure: function () { Ext.Msg.alert(提示, 保存失败); } }); } } }]3.2 Grid 组件让数据长在表格上Ext.grid.Panel是 ExtJS 最核心的数据展示组件它和Ext.data.Store深度绑定。Store 负责数据获取与管理Grid 只负责展示你换数据源时根本不需要动表格代码。定义 Store 时可以指定model、proxy、autoLoad等配置Ext.define(MyApp.model.Order, { extend: Ext.data.Model, fields: [orderNo, customerName, totalAmount] }); var orderStore Ext.create(Ext.data.Store, { model: MyApp.model.Order, proxy: { type: ajax, url: /api/orders }, autoLoad: true });Grid 里最常用到的是columns、store、selModel选择模式、plugins编辑插件这几样。需要行内编辑时给 grid 加一个plugins: [Ext.create(Ext.grid.plugin.CellEditing)]然后在对应列配置editor: textfield或editor: { xtype: numberfield }双击单元格就能直接改数据体验非常接近桌面应用。分页也是一句话的事在 grid 的bbar里加一个Ext.toolbar.Paging把它的store指向当前表格的 store翻页按钮和数据加载就自动串起来了。这种数据层和 UI 层通过 Store 解耦的设计今天看依然是组件化思想的一个很好示范。列格式化则靠renderer函数比如金额字段加货币符号状态字段由数字 0/1 映射成启用/停用。3.3 Tree、Window、Toolbar 和 TabPanel桌面式交互的标配这三个组件后台系统也离不开。Ext.tree.Panel配合Ext.data.TreeStore展示层级数据左侧菜单、部门树、目录树都能做展开收起、懒加载这些交互框架都内置了。Ext.window.Window就是弹窗但它比普通弹窗强在可以拖动、缩放、最大最小化还能往里面塞任意组件。新建、编辑、详情页很多人不喜欢跳转直接弹一个 window 处理交互会更像桌面软件。Toolbar和TabPanel是页面组织的骨架件。Toolbar 可以放在 Panel 的tbar、bbar上用来放操作按钮TabPanel 适合做多页签管理每个页签是一个面板用closable: true可以让页签支持关闭。组合时最典型的是左侧菜单 中间 TabPanel点菜单在中心区域增开页签这个模式在 ExtJS 里实现起来非常顺手。3.4 其他值得知道的组件Ext.MessageBoxExt.Msg用来出提示框、确认框、输入框Ext.tip.QuickTip做悬浮提示Ext.form.field.ComboBox虽然属于表单但经常被单独当作联动选择器用。另外Ext.chart.*系列负责图表可视化。这些组件不用全记住遇到需求时能回想起来ExtJS 里应该有个现成的再翻文档就行。4. 布局系统才是组件排版的真正主角4.1 六种常用布局与选型逻辑很多新手写 ExtJS 碰壁不是毁在组件选择上而是毁在布局上。明明 items 都写对了界面却挤成一团或者什么都不显示十有八九是布局没配明白。布局就是告诉容器孩子们该怎么排。常用的有这几种布局核心行为典型场景注意事项fit子组件填满容器单个内容区只放一个子组件时最常用border上北下南左西右东分区域页面骨架子组件必须给regionhbox子组件横向排一行工具按钮配合flex控制比例vbox子组件纵向排表单区块配合flex控制高度比例card同区域切换显示向导页、页签内容联动setActiveItemtable表格状排布表单多列用colspan控制跨列选型逻辑很简单想清楚我这块区域希望子组件以什么姿态存在。单内容区域无脑用fit有南北东西分区用border需要按比例伸缩用hbox/vbox多页切换用card。不要一上来就全用absolute加像素坐标省了一时后面加需求时绝对会后悔。4.2 flex 与 defaults控制比例和统一默认配置hbox和vbox布局里flex是控比例的核心。给按钮 A 配flex: 1按钮 B 配flex: 2那么 B 占的空间就是 A 的两倍。flex和width/height的关系是设了 flex 就按比例分剩余空间没设就按固定尺寸来。defaults是一个很省事的配置它把公共配置统一派发给所有子组件。比如一个面板里想统一所有子组件的margin可以写defaults: { margin: 10 }就不用每个 items 都写一遍。容器选取默认子组件类型用defaultType例如表单容器里defaultType: textfield写 items 时可以只写字段名而不带 xtype。4.3 嵌套布局和页面自适应复杂页面没有道理只有一个布局。实际项目里经常是外层 border左 region 是树右 region 里再嵌套一个 tabpaneltab 页里又是一个 fit 布局包着 grid。布局可以无限嵌套只要每一层容器都有自己的 layout 就行。嵌套时记住一个原则布局只管理直接子组件子组件内部怎么排是子组件自己的事。这样想多层嵌套也不会乱。自适应方面ExtJS 的响应式能力没有今天的 Vue/React 生态那么时髦但经典做法也够用容器设flex或百分比尺寸窗口缩小会自动挤压也可以用minWidth、minHeight限制最小尺寸防止布局彻底崩坏。真正复杂的响应式可以用Ext.plugin.Responsive按预设断点切换配置。我个人的建议是后台管理系统宽度适配到 1280 以上舒舒服服不要追求过度精细的移动端适配组件这种密度并不适合手机硬怼。5. 组件通信父子、兄弟和 Controller 怎么协作5.1 事件机制与自定义事件组件间通信是组件化绕不开的话题。ExtJS 里最基础的通信方式是事件组件内部fireEvent(myEvent, args)抛出自定义事件监听方用listeners捕获。比如一个列表页点删除时grid 抛出一个deleterecord事件外层容器监听后弹确认框再删职责就被切分得很干净。Ext.create(Ext.panel.Panel, { items: [{ xtype: grid, listeners: { itemclick: function (view, record) { view.fireEvent(selectorder, record); } } }], listeners: { selectorder: function (record) { Ext.Msg.alert(选中, record.get(orderNo)); } } });自定义事件的好处是让组件不依赖具体调用方。组件只管我发生了某件事至于谁来响应、怎么响应那是别人的事。这是解耦的原生姿势。5.2 父子、兄弟组件通信的经典套路父子组件通信基本靠引用找组件。ExtJS 提供了几个很顺手的查询方法this.up(panel)向上找最近的某类父组件this.down(grid)向下找第一个子组件this.next(field)找兄弟。配合Ext.ComponentQuery的类选择器几乎能在组件树里定位任意目标。父传子最简单的方式就是配置传递父组件给子组件设置 config子组件读config里的参数。子传父则可以this.up(panel).doSomething()直接调用父组件方法或者抛事件让父组件处理。兄弟组件之间通信最省心的办法是让父容器当中间人子 A 抛事件父监听再调用子 B 的方法。这套做法和现在 Vue 里的父传子 props、子传父 emit、兄弟靠父组件中转几乎一一对应。所以学 ExtJS 对理解主流框架的组件通信很有帮助反过来也一样。5.3 用 Store 当数据总线数据类组件之间还有一种高级通信方式共享同一个 Store。比如左边树选择了一个分类右边的 grid 要刷新成该分类下的数据。两个组件可以共用同一个 Store树选中时调用store.load({ params: { categoryId: id } })grid 因为挂在同一个 store 上数据一变自动刷新。Store 里还有一个好用的小功能store.getAt(index)、store.findRecord(field, value)可以按条件查数据。多个组件要感知数据变化时可以监听 store 的load或datachanged事件。相当于把数据层抽出来当总线UI 组件只订阅不互相依赖这是我处理跨页签数据同步最常用的方案。5.4 Controller 与 ViewControllerMVC/MVVM 的桥接ExtJS 5 以后有完整的 MVC/MVVM 体系。Ext.app.Controller通过refs拿到组件的引用用control集中绑定事件到方法上。比较新的ViewController更是直接挂在 View 上用this.lookupReference()取指定reference的组件代码组织起来比到处up/down清楚不少。Ext.define(MyApp.controller.Main, { extend: Ext.app.Controller, refs: { grid: grid }, init: function () { this.control({ grid: { itemdblclick: this.onGridDblClick } }); }, onGridDblClick: function (view, record) { // 在这里打开编辑窗口 Ext.create(Ext.window.Window, {}).show(); } });如果你项目还在用老版本 ExtJS 4 或者团队不想引入 MVC那 5.1 到 5.3 的玩法已经足够。组件多了以后建议还是认真上 Controller全局事件满天飞时调试会相当痛苦。6. 组件封装从重复代码到属于自己的组件库6.1 Ext.define 与 extend封装的起步动作写完上百个页面后你会发现大量相似结构反复出现带查询条件和列表的页面、带确认功能的对话框、带分页的表格。这时候就该封装了。ExtJS 封装组件的基本姿势是用Ext.define定义一个类extend继承某个现成组件再给它一个xtype别名Ext.define(MyApp.ux.SearchPanel, { extend: Ext.form.Panel, xtype: searchpanel, layout: hbox, items: [{ xtype: textfield, name: keyword, fieldLabel: 关键词 }, { xtype: button, text: 查询 }] });定义之后这个组件就成了一等公民其他页面可以直接{ xtype: searchpanel }使用。6.2 封装时最容易踩的坑initComponent 重写顺序封装虽好但有一个典型的坑必须在实践里提醒一下重写initComponent时如果直接在子类内部定义items、width等配置项很可能把外部传入的配置覆盖掉。正确做法是在initComponent里先合并两份配置再调用父类方法Ext.define(MyApp.ux.DetailGrid, { extend: Ext.grid.Panel, xtype: detailgrid, initComponent: function () { var me this; Ext.apply(me, { selType: rowmodel, plugins: [Ext.create(Ext.grid.plugin.CellEditing)] }); // 外部传入的 store、columns 会被保留 Ext.applyIf(me.columns, [{ text: 默认列, dataIndex: id }]); me.callParent(); } });核心规则就一条callParent()之前把该补的默认配置都补好外部显式传入的配置优先级要高于内置默认。6.3 项目里最值得优先封装的三种组件根据我的经验投入产出比最高的三件套是带查询条件与结果列表的列表页容器、可复用详情弹窗以及通用分页工具栏。列表页容器把查询表单、grid、分页、刷新按钮整套组合起来业务页面只需要传列配置和数据接口详情弹窗统一管理打开关闭和遮罩层级分页工具栏封装页码和每页条数避免每个页面各写一套翻页逻辑。真把这三样抽出来后续新页面能少写一半代码。建库时还要注意命名空间统一比如项目名加ux前缀样式上可以用ui配置定义不同风格每次封装都补上注释写明哪些配置可覆盖、哪些内部逻辑不建议动。这样组件库才能被团队其他人放心使用。7. 排错与调试那些让人抓狂的组件问题7.1 布局错乱和空白界面的排查路径遇到页面白屏或组件没显示不要慌按顺序查控制台有没有报错大部分白屏是因为某个类名拼错或引用不存在。组件有没有被渲染进正确的容器检查renderTo和items层级。容器 layout 是否合理fit容器里放了两个子组件就会莫名其妙只显示一个。子组件的region是否在 border 布局里都配置了漏掉一个 region整个布局都可能乱套。Chrome 开发者工具里还有一个杀手锏在控制台输出Ext.ComponentQuery.query(*)能看到当前页面所有组件实例。配合浏览器 Elements 面板查 DOM 结构能快速判断是组件没创建、没渲染还是渲染了但被布局压到不可见。7.2 组件销毁与内存回收后台系统长时间挂着内存占用一路走高通常是组件销毁不彻底造成的。关闭弹窗时如果用的是window.destroy()记得同时确认里面的 store、插件有没有被正确释放。动态往容器里塞组件的场景旧组件在替换时最好先destroy()再添加。监听器挂在全局对象上的情况也要小心组件销毁后监听不解除等于活着的引用还在攒内存。简单粗暴但不失有效的做法不用的组件一律 destroy还能复用的组件用hide()而不是不断创建。7.3 版本差异与几个低调但好用的配置ExtJS 4、5、6、7 之间组件写法有细微差别最典型的是 grid 的 store 从Ext.data.Store实例到可以接受 storeId 字符串Modern 工具包和 Classic 工具包更是两套体系。遇到问题先核对你用的版本网上答案常因为版本不同而对不上。还有几个配置项容易被忽略但特别好用bodyPadding直接给面板加内边距省得每个子组件都写 margincollapsible: true让面板可折叠节省页面空间frame: true给面板加圆角边框让老界面不那么生硬。这些小配置看起来不起眼实际体验提升很明显。7.4 快速上手的个人建议如果身边没有老手带最稳妥的路线是先把官方示例里的 kitchen sink 页面跑起来逐个组件点一点感受下交互再模仿一个后台管理的经典布局亲手写一遍 bordertreetabpanelgrid之后开始封装自己页面里重复了两遍以上的片段。这套流程基本两三天就能找到手感。真正想深入的话再去啃组件继承关系和生命周期实现那时候你会看得非常快。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑