☰
Ext JS 7.8+入门指南:企业级后台表格表单与构建实战
2026/10/28 4:13:34 网站建设 项目流程

简介:《Sencha Ext JS 7.8+入门指南》是一份面向具备JavaScript基础、计划构建企业级复杂前端应用的开发者的PDF教程,侧重解决从环境配置到组件化开发的上手问题。内容从框架特点、Toolkits与Sencha差异讲起,依次覆盖试用及商业许可项目创建、Sencha Command安装、npm环境管理、IDE选择、项目文件结构与导航编辑,并延伸到视图、组件及MVC架构的基本用法。文中给出数据网格、柱状图、表单捕获等示例,配合分阶段操作说明与代码片段,便于读者理解核心组件并完成基础项目搭建、调试与优化。压缩包内含1个PDF文件,大小约2.65MB,轻量易查阅;目前已有196人学习,适合希望系统梳理Ext JS 7.8+入门路径、补齐企业级UI与数据可视化实践的前端开发者参考。

1. 先看清 Ext JS 的定位:它替你省掉的是表格和表单的重复劳动

接手一个后台管理项目,需求评审完发现七成页面是数据表格加筛选栏加弹窗表单,这时候拿通用框架从零搭,光列排序、分页、行编辑、复合表头、列锁这些通用能力就要写一整轮。这份《Sencha Ext JS 7.8+ 入门指南》讲的正是另一条路:用 Ext JS 7.8 自带的 140 多个开箱组件直接拼业务。从安装、模板生成到组件创建,把入门路径完整走一遍,你能很快判断它适不适合手上的活儿——适合有 JavaScript 基础、要交付复杂数据驱动型企业级应用的前端,也适合被表格需求折磨过的团队负责人。

2. 环境与项目生成:从 Sencha Command 装好到第一个骨架跑起来

Ext JS 和常见的 npm 脚手架不太一样,它的工程化能力大半压在 Sencha Command(下称 Cmd)这个命令行工具上:建项目、生类、构建、打包、起开发服务器全归它管。所以第一步不是create-xxx-app,而是把 Cmd 装对。

2.1 安装 Sencha Command:安装器路径与 npm 路径怎么选

下载入口在官方评估页,填完信息后会拿到 Cmd 安装包,顺带能看到 Architect、Themer 这些配套工具的下载项。安装过程本身没什么坑,只有两个地方要留意:

  1. 解压安装包后运行安装器,一路按提示走;
  2. 到「选择组件」那一屏时,不要勾 Compass 扩展。

Compass 是早期版本用来编译 Sass 的扩展,7.8 的样式编译链路里已经不需要它了。这一步勾上去,后面sencha app build有一定概率直接报编译失败,属于典型的自己给自己挖坑。

另一个细节是 Windows 上安装包可能被安全软件拦一下,提示来源不明,选继续运行即可,安装器本身没做签名之外的额外动作。

装完之后先验证命令可用,别急着建项目:

# 确认 Cmd 装到了哪个路径,以及当前 shell 能不能找到它 sencha which # 看版本号,确认是 7.x 系列的 Cmd sencha -v

sencha which输出的是 Cmd 的安装根目录以及当前项目识别到的框架路径。如果你在某个项目目录里执行,它会告诉你这个项目绑定的 Ext JS 框架在哪;在空目录里执行,就只报 Cmd 自身的位置。这个命令在排查「为什么构建用的是旧框架」时特别好使。

如果你不想用安装器,官方也给了 npm 托管的路径,核心思路是把 Cmd 和框架运行时都交给包管理器管,方便在 CI 里复现同一套环境:

# 具体包名与版本以官方 Getting Started with NPM 文档为准,这里只示意调用方式 npm install -g @sencha/cmd # 装完同样验证 sencha which

两种方式的差别在实际项目里很明显:安装器路径适合单机开发,图形化装完就能用;npm 路径适合多人和流水线,环境版本写在依赖里,谁拉下来都一样。我一般在新机器上先用安装器把流程跑通,确认没问题后再切到 npm 方式固化到团队文档里,这样出问题能快速判断是环境还是配置。

2.2 sencha generate app 的参数怎么填

骨架生成只需要一条命令,但参数填错就要删目录重来,值得说清楚。

# 在项目目录内执行:生成一个名为 MyApp 的经典工具包应用,输出到当前目录 sencha generate app classic -ext MyApp ./

三个位置分别对应三件事:

位置含义可选取值与注意点
classic工具包类型classic面向桌面端,主要用 JavaScript 渲染视图;modern用 CSS 渲染,面向平板和手机
-ext MyApp应用名,同时作为命名空间根必须符合变量命名规则,后续所有类名都会以它为前缀,如MyApp.view.main.Main
./生成目录相对路径,指向一个已经存在的空目录最稳妥

工具包的选择不是一次定终身,但改起来成本不低。classic和modern共享同一套核心类,差异集中在视图渲染和部分组件实现上,所以类名会有出入,业务逻辑层(Store、Model、Controller)基本可以复用。经验做法是:桌面端为主、表格密度高、需要复杂列操作,选classic;移动端优先或要做响应式触屏界面,选modern;两边都要,用universal思路拆共享层,但入门阶段不建议一上来就这么搞。

商业授权版本还会提供一批预置模板,装完授权后可以先列一下当前版本到底支持哪些:

# 查看 generate app 支持的参数和模板,不同版本输出略有差异 sencha generate app --help

模板本质上是预置好目录结构、主题和示例页面的项目骨架,适合快速看官方怎么组织一个多级导航的后台。但模板里的示例代码量不小,新手容易被带偏,建议先用默认模板把 Grid、Chart、Form 各写一遍,再回头翻模板对照。

除了整项目,Cmd 还能生成单个类,这个在日常开发里用得比generate app更频繁:

# 生成一个视图类,会自动落到对应工具包的 src/view 目录下并写好骨架 sencha generate view main.GridView # 生成 Store 和 Model sencha generate model Example name:string,price:float sencha generate store Example

生成命令的价值在于它会顺手把命名空间、extend、alias这些容易写错的样板补全,还会落到正确的目录里。手写一个新类忘了放对位置,是后面「类加载不到」问题的高发原因。

2.3 三种构建环境与首次验证

项目生成完不能直接双击index.html打开,这是新手第一个翻车点。Ext JS 的入口页靠 microloader 动态加载类文件,必须有构建产物或开发服务器配合。第一次验证走构建:

# 开发环境构建:不压缩,保留完整类文件,方便断点调试 sencha app build development # 测试环境构建:介于两者之间,便于定位问题 sencha app build testing # 生产环境构建:压缩合并,输出可直接部署的静态资源 sencha app build production

三种环境的差别不只是压不压缩。development会把每个类单独输出,浏览器里能看到原始文件名和行号;production会做依赖分析,只打包真正被引用到的类,体积能差出一个数量级;testing保留可读性但走完整构建流程,用来验证「生产环境才出问题」的那类 bug。产物默认落在build/<environment>/<AppName>/下,用任意静态服务器指向该目录就能访问。

日常开发更常用的是监听模式:

# 起开发服务器并监听文件变化,改完自动增量构建并刷新 sencha app watch

sencha app watch背后跑的是一个本地服务加增量构建器,第一次启动会慢一些,之后改一个类只重新编译受影响的部分。它默认监听经典工具包的构建目标,如果你的app.json里配了多个 build,需要指定具体哪个。启动日志里会打印访问地址和端口,照着打开就行。

验证清单我一般固定三步:页面能出、导航能点、控制台无红色报错。第三步最容易漏,Ext JS 的类加载失败有时不阻断渲染,只是某个组件静默不显示,等写业务时才发现,回头查成本翻倍。

3. 项目结构拆解:app 目录与 classic/src 的双份视图到底谁生效

骨架跑起来之后,第一件事是把目录看明白。Ext JS 的目录结构和常见前端项目差别不小,尤其是视图类会出现在两个位置,不理解这套约定会写出「改了没生效」的玄学问题。

3.1 目录职责划分

一个经典工具包项目生成后的主干大致是这样:

MyApp/ ├── app/ # 跨工具包共享的代码 │ ├── Application.js # 应用入口,全局启动逻辑 │ ├── model/ # 数据模型 │ ├── store/ # 数据源 │ └── view/ │ └── main/ │ ├── Main.js # 主视图(共享层) │ ├── MainController.js │ └── MainModel.js ├── classic/ │ └── src/ │ └── view/ │ └── main/ │ └── Main.js # 经典工具包专用视图,同名但优先级更高 ├── resources/ # 静态资源、图片、字体 ├── sass/ # 主题变量与样式覆盖 ├── app.json # 构建与框架配置 ├── build.xml # 构建脚本扩展点 ├── index.html # 入口页 └── bootstrap.js # 类加载引导,构建时生成

关键规则只有一条:同名类在classic/src下会覆盖app/下的定义。构建器按工具包顺序解析类路径,classic/src排在前面。所以如果你在app/view/main/Main.js里改了东西却没生效,先确认classic/src/view/main/Main.js是不是也有一份。

这条规则本身是给多工具包项目用的:共享逻辑放app/,平台差异实现放各自工具包目录。单纯做桌面端项目时,它反而变成了干扰项。我的习惯是项目初期就统一把视图放在classic/src/view下,app/只留 Model、Store 和 Controller,这样「视图在哪个目录」这个问题永远不会再出现。

3.2 导航菜单是怎么渲染出来的

默认模板的主界面是一个边框布局:左侧导航树、中间标签页容器、顶部工具栏。菜单项加在Main.js的树节点上,点击后往中间区域塞一个组件。核心结构大致如下:

Ext.define('MyApp.view.main.Main', { extend: 'Ext.panel.Panel', xtype: 'app-main', controller: 'main', layout: 'border', items: [{ // 左侧导航区 region: 'west', width: 240, xtype: 'treepanel', title: '功能导航', rootVisible: false, store: { type: 'tree', root: { expanded: true, children: [ { text: '数据网格', leaf: true, iconCls: 'x-fa fa-table', handler: 'onGridViewClick' }, { text: '柱状图', leaf: true, iconCls: 'x-fa fa-chart', handler: 'onChartViewClick' }, { text: '捕获表单', leaf: true, iconCls: 'x-fa fa-edit', handler: 'onFormViewClick' } ] } } }, { // 中间内容区:用 tabpanel 承载多个视图 region: 'center', xtype: 'tabpanel', reference: 'mainTabs', items: [] }] });

这里有两个值得单独说的点。handler写的是字符串而不是函数,Ext JS 会到当前视图绑定的 ViewController 里按这个名字找方法,controller: 'main'就是那个绑定的来源——用字符串的好处是视图和逻辑彻底分开,视图类里不出现业务代码。另一个是reference: 'mainTabs',它给组件起了一个局部别名,在 Controller 里用this.lookup('mainTabs')就能拿到实例,不用满世界写Ext.ComponentQuery.query。

对应的 Controller 方法:

Ext.define('MyApp.view.main.MainController', { extend: 'Ext.app.ViewController', alias: 'controller.main', onGridViewClick: function () { this.openTab('gridview', '数据网格'); }, onChartViewClick: function () { this.openTab('examplechart', '柱状图'); }, onFormViewClick: function () { this.openTab('captureform', '捕获表单'); }, // 统一开标签页:已存在就激活,不存在才创建,避免重复打开 openTab: function (xtype, title) { var tabs = this.lookup('mainTabs'), exist = tabs.child('component[xtype=' + xtype + ']'); if (exist) { tabs.setActiveTab(exist); return; } tabs.add(Ext.create({ xtype: xtype, title: title, closable: true })); tabs.setActiveTab(tabs.items.last()); } });

this.lookup()只在 ViewController 内部可用,参数是视图里的reference值。tabs.child(selector)用组件查询字符串匹配子组件,这里按xtype属性找,写法是component[xtype=xxx]。Ext.create({ xtype: 'xxx' })是延迟实例化的推荐姿势,传对象形式、用xtype而不是类名,方便后面换实现。这套「开标签页」的小工具几乎每个 Ext JS 项目都会写一遍,早点抽成通用方法能省下不少复制粘贴。

3.3 app.json 与构建映射

app.json是整个项目的配置中心,构建器读它决定用哪个框架版本、生成几个构建目标、主题从哪来。和日常开发最相关的是builds段:

{ "name": "MyApp", "namespace": "MyApp", "framework": "ext", "toolkit": "classic", "indexHtmlPath": "index.html", "builds": { "classic": { "toolkit": "classic", "theme": "theme-triton", "output": "build/classic" } } }

namespace必须和sencha generate app里的应用名一致,改了它所有类名都要跟着改,属于不可轻易动的字段。theme指向主题包,换成theme-material或自定义主题都在这里改,改完需要重新构建才生效,热更新不一定覆盖主题文件。output是产物输出目录,生产部署时静态服务器指向这里。

build.xml是构建脚本的扩展点,默认内容很少,但可以在build目标前后插自定义步骤,比如打包前往resources里拷一份版本号文件、构建后跑一次资源校验。这类事别硬塞进app.json,构建钩子的位置就是干这个的。

4. 核心组件实战:Grid、Chart、Form 三件套的骨架写法

Ext JS 的学习曲线陡在「类系统」而不是组件 API。同一件事往往有两种写法,选错了组件能跑但代码会散。

4.1 Ext.define 与 Ext.create 的分工

这两个方法新手最容易混。Ext.define是声明,只把类注册进框架的类系统,产生类名、alias、xtype这些元数据;Ext.create是实例化,这时才真正走构造函数、创建 DOM、绑定事件。

// 声明:只注册,不执行任何构造逻辑 Ext.define('MyApp.view.main.GridView', { extend: 'Ext.grid.Panel', xtype: 'gridview', title: '行情列表' }); // 实例化:传入的第二个参数会覆盖类里的默认配置 var grid = Ext.create('MyApp.view.main.GridView', { title: '按代码筛选后的行情', height: 400 });

第一个参数必须是完整类名,不能写xtype。第二个参数是配置对象,会浅合并到类的默认配置上,所以title被覆盖而其他配置保留。如果通过xtype实例化,写法是Ext.create({ xtype: 'gridview' })或Ext.widget('gridview')。

xtype的意义在于延迟加载:写xtype: 'gridview'时构建器知道依赖关系,但实例化推到真正需要的那一刻。视图里嵌组件一律用xtype,不要写Ext.create,否则组件在类定义阶段就被创建了,配置还没合并完,容易出现「父容器的viewModel拿不到」的问题。

4.2 数据网格:Store、列配置与筛选

Grid 是这套框架的主战场。一个能用的表格由两部分组成:描述数据的 Store 和描述展示的 Grid 视图。先写 Store:

Ext.define('MyApp.store.ExampleStore', { extend: 'Ext.data.Store', alias: 'store.examplestore', fields: [ { name: 'symbol', type: 'string' }, { name: 'price', type: 'float' }, { name: 'change', type: 'float' }, { name: 'volume', type: 'int' } ], // 先用内存数据把界面跑通,接后端时只改 proxy 这一段 proxy: { type: 'memory' }, data: [ { symbol: 'AAA', price: 12.40, change: 1.20, volume: 120000 }, { symbol: 'BBB', price: 88.10, change: -0.65, volume: 54000 }, { symbol: 'CCC', price: 45.75, change: 2.10, volume: 210000 } ] });

alias: 'store.examplestore'是关键,它让这个 Store 可以用store: { type: 'examplestore' }的写法被引用,不用在任何地方Ext.create。fields定义字段名和类型,类型影响排序和格式化行为,int和float在数值排序上表现完全不同,字段类型写错会出现「按字符串排序」的经典问题。

对接后端时把proxy.type换成ajax,补url和reader就行,视图一行不用改:

proxy: { type: 'ajax', url: '/api/quotes', reader: { type: 'json', rootProperty: 'data', // 后端返回 { code: 0, data: [...] } 时指到 data totalProperty: 'total' // 分页总数,不分页可以删掉 } }

然后是 Grid 视图:

Ext.define('MyApp.view.main.GridView', { extend: 'Ext.grid.Panel', xtype: 'gridview', // 通过 alias 引用 Store,框架负责实例化和缓存 store: { type: 'examplestore' }, // 列筛选插件:开启后每列表头出现筛选入口 plugins: 'gridfilters', columns: [ { text: '代码', dataIndex: 'symbol', flex: 1, filter: 'string' }, { text: '价格', dataIndex: 'price', width: 120, xtype: 'numbercolumn', format: '0.00', align: 'right', filter: 'number' }, { text: '涨跌', dataIndex: 'change', width: 120, align: 'right', filter: 'number', // 渲染器只负责把值转成 HTML,不改变底层数据 renderer: function (value) { var color = value >= 0 ? '#c0392b' : '#27ae60'; return '<span style="color:' + color + '">' + value.toFixed(2) + '</span>'; } }, { text: '成交量', dataIndex: 'volume', width: 140, xtype: 'numbercolumn', format: '0,000', align: 'right', filter: 'number' } ] });

plugins: 'gridfilters'需要Ext.grid.filters.Plugin这个类被加载,正常情况下构建器会通过插件别名解析到,但如果你在动态加载的开发环境里看到筛选入口不出现,就在 Grid 类上加requires: ['Ext.grid.filters.Plugin'],显式声明依赖最省心。

renderer的签名是(value, metaData, record, rowIndex, colIndex, store, view),只用到第一个参数就够。注意它返回的是 HTML 字符串,必须确保内容可信,来自用户输入的值要先转义,这点和手写前端没有区别。

flex: 1和固定width混用时,Grid 会先分配固定宽度再把剩余空间按 flex 比例分给弹性列,所以至少留一列flex,不然窗口拉宽会出现右侧空白。

4.3 柱状图:需要显式引入图表包

图表在经典工具包里不是默认打包的,这一点坑过不少人——代码写完页面一片空白,控制台没有明显错误。除了requires,还要在app.json或构建配置里确认图表相关类能被解析到。

Ext.define('MyApp.view.main.ExampleChart', { extend: 'Ext.chart.CartesianChart', xtype: 'examplechart', // 经典工具包里图表要显式声明,否则渲染阶段拿不到类 requires: ['Ext.chart.*'], store: { type: 'chartstore' }, insetPadding: 24, axes: [{ type: 'numeric', position: 'left', fields: ['value'], grid: true, minimum: 0 }, { type: 'category', position: 'bottom', fields: ['label'] }], series: [{ type: 'bar', xField: 'label', yField: ['value'], style: { fill: '#3b7dd8', opacity: 0.9 }, label: { display: 'insideEnd', field: 'value', renderer: Ext.util.Format.numberRenderer('0') } }] });

CartesianChart是直角坐标系图表,柱状图和折线图共用它,靠series.type区分。axes里fields必须和series里用到的字段对得上,yField写成数组是为了支持多系列堆叠,单系列也建议保持数组写法,后面加系列不用改结构。

insetPadding控制绘图区四周留白,柱子贴着边框或者标签被裁掉时调这个。series.label.display的取值决定数值标签位置,insideEnd是柱内顶端,数据密度大时换成outsideEnd容易被裁,得配合insetPadding一起调。

Store 和 Grid 那份类似,字段换成分类和数值:

Ext.define('MyApp.store.ChartStore', { extend: 'Ext.data.Store', alias: 'store.chartstore', fields: [ { name: 'label', type: 'string' }, { name: 'value', type: 'float' } ], data: [ { label: '一月', value: 32 }, { label: '二月', value: 45 }, { label: '三月', value: 28 }, { label: '四月', value: 51 } ] });

4.4 捕获表单:View 与 Controller 的职责切分

表单是 MVC 分工体现最明显的地方。视图只声明字段,校验通过后的处理全交给 Controller。

Ext.define('MyApp.view.main.CaptureForm', { extend: 'Ext.form.Panel', xtype: 'captureform', // 绑定控制器,按钮上的 handler 字符串才能被解析到 controller: 'main', title: '信息录入', bodyPadding: 16, defaultType: 'textfield', defaults: { anchor: '100%', labelWidth: 80 }, items: [ { fieldLabel: '名称', name: 'name', allowBlank: false }, { fieldLabel: '数量', name: 'qty', xtype: 'numberfield', minValue: 1, value: 1 }, { fieldLabel: '日期', name: 'date', xtype: 'datefield', format: 'Y-m-d', value: new Date() }, { fieldLabel: '备注', name: 'memo', xtype: 'textareafield', allowBlank: true } ], buttons: [ { text: '重置', handler: 'onResetClick' }, { text: '保存', handler: 'onSaveClick', formBind: true } ] });

defaults里的配置会作用到每个子项,anchor: '100%'让字段撑满容器宽度,比逐个写width省事。formBind: true挂在保存按钮上,会跟着表单有效性自动启用禁用,allowBlank: false和minValue的变化会实时反映到按钮状态,这个联动是框架自带的,不用手写监听。

Controller 侧:

Ext.define('MyApp.view.main.MainController', { extend: 'Ext.app.ViewController', alias: 'controller.main', onSaveClick: function (btn) { // 从按钮向上找最近的 form 组件,比写 reference 更抗结构变动 var form = btn.up('form'); if (!form.isValid()) { Ext.Msg.alert('提示', '请检查标红字段'); return; } // getValues 返回纯对象,字段名做 key var values = form.getValues(); Ext.Msg.alert('提交内容', Ext.encode(values)); }, onResetClick: function (btn) { // 注意:form.reset() 和 form.getForm().reset() 效果一致,后者更明确 btn.up('form').getForm().reset(); } });

btn.up('form')是从当前组件沿组件树向上查找,选择器可以是xtype、组件 id 或属性选择器。这种写法比在视图里写死reference更耐改,中间插一层容器也不会断。form.getValues()拿的是经过字段自身转换后的值,日期字段返回Date对象而不是字符串,直接JSON.stringify会变成 ISO 字符串,提交后端前通常要手动格式化一遍:

var values = form.getValues(); values.date = Ext.Date.format(values.date, 'Y-m-d'); // 转成后端习惯的字符串格式

这类「提交前整形」的逻辑如果多个表单都要用,抽成Ext.define的工具类挂在app/util/下,别在每个 Controller 里复制。

5. 避坑与排查:五条踩出来的经验

这一章是我在一台干净机器上重装两遍环境、删了三次项目目录换来的。每一条都是「明明按文档做了但就是不工作」的类型。

5.1 构建与环境类问题

第一条:双击 index.html 打开是白屏,控制台报 microloader 相关错误。

现象是页面完全空白,Network 面板能看到bootstrap.js加载失败或者后续类文件 404。原因是入口页依赖 microloader 动态解析类路径,bootstrap.js是构建时按当前工具包和框架路径生成的,直接用file://协议打开时,浏览器对本地文件的 XHR 限制会拦住类加载。解决很简单:不要双击,用sencha app watch起开发服务器,或者先sencha app build development再用任意静态服务器指向build/development/MyApp/。这条对新人杀伤力最大,因为「我什么都没改却打不开」最打击信心。

第二条:安装时勾了 Compass,后面构建一直失败。

现象是sencha app build跑到样式编译阶段报错退出,错误信息指向 Sass 编译扩展。原因是 Compass 是旧版本 Ext JS 的样式编译依赖,7.8 的链路里已经不再使用,装上反而会和当前的编译流程冲突。解决方式是重跑安装器,在组件选择界面取消 Compass,或者干脆卸载重装。血泪经验:安装器的默认勾选项不一定都该留,装之前扫一眼每个组件是干什么的。

第三条:生产构建之后样式没了、字体图标变方块。

现象是development构建一切正常,production部署到服务器后页面能用但样式全丢。原因通常是资源路径写成了绝对路径,比如 Sass 里引字体用了/resources/fonts/xxx.woff,开发服务器根目录正好对得上,部署到子路径下就全 404。解决方式是把资源引用改成相对路径,或者在app.json里配置正确的output和资源基准路径。排查方法很直接:打开生产构建后的页面,Network 面板按 404 过滤,能看到所有失败请求的实际路径,一眼就能对出问题。

5.2 组件与数据类问题

第四条:Store 写好了、Grid 也挂上了,但表格一行数据都不显示。

现象是 Grid 渲染出来了,列头正常,表体空白,控制台无报错。原因有几种,按概率从高到低排:alias拼写和store: { type: 'xxx' }里的type不一致,框架会静默创建一个空 Store 而不是报错;fields里的字段名和data里的 key 大小写不一致,值取不到就显示空白;proxy.type写了memory但数据塞在了proxy.data而不是 Store 顶层的data。解决方式是先console.log(Ext.getStore('examplestore'))看 Store 里有没有记录数,有记录说明是视图绑定问题,没记录就是数据源问题。用alias声明的 Store 可以用Ext.getStore按 alias 后半段拿到实例,这个调试手段比猜快得多。

第五条:新加的视图用 xtype 引用时报「无法创建组件」。

现象是点击菜单后中间区域空白,控制台出现类找不到的警告。原因有两个方向:一是xtype在多个类里重复定义,框架不知道用哪个;二是新视图的类文件路径和类名不匹配,构建器按app/view/main/GridView.js找不到MyApp.view.main.GridView。Ext JS 的类加载严格依赖「类名 ↔ 文件路径」的映射关系,类名写Main.GridView就必须落在view/main/GridView.js下(工具包目录同理),大小写也要一致。解决方式是先用sencha generate view生成骨架再往里填内容,别手动建文件;xtype保持全局唯一,命名时带上业务前缀。

排查顺序我固定成这样:先看 Network 有没有 404,再看 Console 有没有类加载警告,最后才动代码。Ext JS 的错误信息大多不精确,但类加载阶段的问题在网络面板里通常暴露得很清楚。

6. 二次开发路线:把示例骨架改成自己的业务底盘

入门示例跑通之后,真正的工作是把这套骨架改造成团队能长期维护的结构。按我的习惯,改造分三步走,每步都能独立验证。

第一步是重构导航结构。示例里菜单项是硬编码在Main.js的树节点上的,业务一多就成一坨。做法是把菜单抽成一个独立的 Store,由后端下发或者本地 JSON 配置,Main.js只负责渲染,点击后按配置里的xtype动态创建视图。验证方法是往配置里加一个不存在的xtype,看日志是否给出明确提示而不是整页崩掉——能优雅降级才算改造成功。

第二步是统一数据层。把proxy的公共配置(url前缀、请求头、错误处理)抽成一个基础 Store 类,业务 Store 继承它只写fields和资源路径:

Ext.define('MyApp.store.Base', { extend: 'Ext.data.Store', proxy: { type: 'ajax', reader: { type: 'json', rootProperty: 'data', totalProperty: 'total' }, // 统一错误处理,避免每个 Store 各写一份 listeners: { exception: function (proxy, response) { Ext.Msg.alert('请求失败', '状态码 ' + response.status); } } } });

业务 Store 里extend: 'MyApp.store.Base',只保留fields,proxy.url在子类里覆盖。这样后端换协议、加鉴权头,改一个地方就够。改造完的验证方式是随便挑一个列表页,把后端接口地址故意写错,看错误提示是否统一弹出,而不是静默空白。

第三步是主题与构建。把app.json里的theme换成自定义主题包,把品牌色、圆角、字号这些变量落到sass/下的var文件里,然后跑一次sencha app build production对比产物体积。这一步最容易被跳过,但生产构建暴露的问题(路径、资源、类裁剪)在开发阶段完全看不到,越晚发现越麻烦。

从那以后我每次改完app.json或新增工具包目录,都会强制走一遍sencha app build production再提交,哪怕当次改动只加了一个视图——生产构建是唯一能验证「类依赖是否完整、资源路径是否正确」的手段,跳过它等于把风险留到上线当天。希望这些能帮你少删几次项目目录。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询