ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Sencha Ext JS 7.8 入门:Cmd 环境、MVC 与三类视图实战

Sencha Ext JS 7.8 入门:Cmd 环境、MVC 与三类视图实战

简介:《Sencha Ext JS 7.8+入门指南》面向具备JavaScript基础、希望构建复杂企业级前端应用的开发者,聚焦框架从环境搭建到核心组件落地的完整入门路径。资源包为1个PDF文件,约2.65MB,内容围绕安装配置、项目创建、基础项目结构与视图组件展开,并融入MVC架构与数据可视化等实践主题。已有196人学习,适合作为团队技术选型或个人进阶的参考资料。文档从许可证获取、Sencha Command安装、npm环境管理、IDE选择讲到项目模板与导航编辑,配以数据网格、柱状图、表单捕获等示例和操作步骤;读者可据此理解Ext JS的工程组织方式,掌握常用组件的创建与配置,并延伸到官方文档、视频教程与社区支持等后续学习线索。整体目录清晰,兼顾入门指引与示例查阅,便于结合实际项目逐步调试和优化代码。

1. 拿到 7.8 版入门指南先别急着装:三个前置判断

企业后台里那种一屏四十列、带筛选、行内编辑、还要导出表格的页面,用 Vue 或 React 从零拼,通常得拖进三四个库再自己抹平样式和交互。这份 Sencha Ext JS 7.8+ 入门指南走的是另一条路:框架自带一百四十多个组件,网格、图表、表单、布局、数据代理是同一套东西,装完 Sencha Cmd 就能用命令行生成项目骨架。它讲的是安装、模板和组件创建这条主线——怎么拿试用授权、怎么装命令行工具、sencha generate app的参数怎么传、classic 与 modern 两个工具包怎么选,以及 Grid、Chart、Form 三个视图从建 Store 到挂进菜单的完整走法。适合有 JavaScript 基础、要新建或接手企业级后台的前端,也适合被临时派去维护一套跑了几年、目录都不敢乱动的 Ext JS 项目、需要先摸清文件结构和 MVC 接线的开发者。

2. 环境落地:Sencha Cmd 与 npm 两条路,选错要多折腾半天

环境这一步是新人第一个卡点。它的麻烦不在于装不上,而在于装完之后团队里每个人的版本不一样,构建出来的东西也不一样。手册里给了安装包和 npm 两条路,我按实际使用场景把差别摊开说,选之前先看清楚。

2.1 装到系统里还是装进项目里:两种环境的取舍

安装包方式是把 Sencha Cmd 装进系统目录、写进 PATH,任何路径下敲sencha都能用;npm 方式是把 Cmd 和框架包都收进项目的node_modules,靠package.json锁住版本。两者的差别集中在协作和流水线上。

维度安装包方式npm 方式
安装位置系统全局,写入 PATH项目node_modules/.bin
版本一致性靠人自觉,换台机器就可能不一致package.json锁定,npm ci还原
升级下载新安装包覆盖改版本号重装
流水线需要在镜像里预装跟着依赖一起装
离线装一次本机长期可用需要私有源或缓存
适用个人机器、一次性搭环境多人协作、有 CI 的项目

如果只是自己跟着手册走一遍流程,安装包最省事。只要是团队项目,我一般直接上 npm,免得出现“我这边能构建你那边报错”的老问题。

2.2 Sencha Cmd 安装:组件勾选与 Windows 拦截

从试用授权入口下载 Cmd,解压后运行安装器,然后按向导走。这一步有两个地方容易停住。

  1. Windows 上首次运行安装器,系统防护会弹拦截提示,选择继续运行即可,文件本身来自你拿到的下载包。
  2. 安装器的组件勾选页面里,Compass 扩展不用勾。它是早期版本编译 Sass 用的,7.8 这条链路上已经不需要,勾上反而多一份依赖要维护。
  3. 安装路径尽量不含空格和中文。后面 Sass 编译和构建脚本会读这个路径,带空格的目录在部分命令里会翻车。

装完之后必须重开一个终端窗口,PATH 才会生效。旧窗口里敲sencha提示找不到命令,八成就是这个原因。

2.3 npm 托管:把 Cmd 和框架包钉在项目里

npm 方式的思路是把工具链变成项目依赖。先起一个空目录,把 Cmd 装成开发依赖:

mkdir ext-demo && cd ext-demo npm init -y # Cmd 装到项目本地,不污染全局环境 npm install --save-dev @sencha/cmd # 用 npx 调本地 bin,确认版本和安装路径 npx sencha which

npx sencha which会打印当前生效的 Cmd 版本和安装目录。这一步很关键:如果机器上同时存在全局安装和本地安装,which的输出能告诉你到底用的是哪一个,避免构建时两套版本打架。团队协作时把package.json和锁文件提交,别人npm ci就能还原同一套工具链。

框架本体同样走包管理。拿到授权后按工具包拆分的包安装,classic 与 modern 是分开的,常见的是@sencha/ext-classic、@sencha/ext-modern这类命名,具体包名以你拿到的授权渠道为准,不要照抄网上的旧包名。

# 按需安装对应工具包的框架包 npm install @sencha/ext-classic # 装完确认目录里确实有 SDK 内容 ls node_modules/@sencha/ext-classic

装好之后,app.json里的framework指向本地包路径,构建就不再依赖机器上那个全局 SDK 目录。这么做的好处是换机器不用重新配 SDK 路径,坏处是仓库体积会变大,记得把node_modules排除掉。

2.4 开工前的三条自检命令

环境装完别急着生成项目,先跑三条命令确认地基是平的:

# 1. 当前生效的 Cmd 版本与路径 sencha which # 2. 环境诊断,看 JAVA_HOME、Cmd 版本、workspace 路径 sencha diag show # 3. JDK 版本,Cmd 对高版本 JDK 支持滞后,8 或 11 最稳 java -version

sencha diag show的输出在 Windows PowerShell 里想截取前几行,用| Select-Object -First 20,head在那个环境里不可用。这份诊断信息里如果JAVA_HOME是空的,或者指向一个不存在的目录,构建阶段一定会报错,在这里先修掉比后面排查省事得多。

提示:JDK 版本是这套工具链里最玄学的一环。同一个项目,同事用 JDK 8 构建正常,换到高版本 JDK 可能在 Sass 编译阶段直接中断。我一般固定 8 或 11,并在团队文档里写死。

3. 从 generate app 到 production 构建:模板、工具包与产物目录

项目生成这一步看起来只有一行命令,实际踩点集中在工具包选择和构建环境上。手册里明确说了以 Classic Toolkit 为主线,这个选择本身就值得先讲清楚。

3.1 classic 与 modern:不是桌面和手机的分界线

两个工具包共享同一套核心,差别在渲染方式和组件实现。很多人误以为 classic 只能跑桌面、modern 只能跑移动端,这个理解会把你带偏。

维度Classic ToolkitModern Toolkit
渲染方式以 JavaScript 为主构建视图以 CSS 为主渲染视图
优化目标桌面浏览器上的复杂数据应用平板、手机等触屏设备
组件覆盖网格、树、图表、表单控件更全移动端交互组件更顺手
主题体系成熟的桌面主题与 Sass 变量面向触屏的尺寸与间距

实际项目里,如果主体是密集数据表格、行内编辑、多级表头、右键菜单,选 classic;如果主体是列表流、卡片、手势操作,选 modern。手册以 classic 为主线,是因为企业后台的典型形态就是大表格加大表单,跟着这条线走,示例代码基本能直接搬。

3.2 sencha generate app 的参数拆解

命令的基本形态是sencha generate app [toolkit] -ext [ApplicationName] [Folder/Directory]。先把目录建好再执行:

# 建目录并进入,注意目标目录必须是空的 mkdir -p D:/work/ext-demo && cd D:/work/ext-demo # 生成一个 classic 工具包的项目,类名前缀 MyApp,输出到当前目录 sencha generate app classic -ext MyApp ./

四个位置的含义:classic指定工具包;-ext表示使用 Ext JS SDK 而不是 workspace 里的共享框架;MyApp是应用类名前缀,后面所有类都会挂在MyApp.*命名空间下,所以要符合变量命名规则,别用数字开头、别带连字符;./是相对输出路径。

有两点必须提前知道。第一,目标目录非空会直接拒绝生成,目录里有隐藏文件(比如.git)也算非空,先清干净。第二,不同版本的 Cmd 对这个位置参数的处理不完全一致,如果提示未知参数,可以换成开关写法sencha generate app -ext -classic MyApp ./;再不行就先不带工具包生成,然后改app.json里的toolkit字段,再执行一次构建让配置生效。

3.3 生成后的目录结构与 app.json 的关键字段

生成完的项目长这样,先认路再动手:

  • app/:业务代码主目录,view/、controller/、model/、store/、viewModel/都在这里
  • classic/:classic 工具包专属的源码与样式覆盖,src/放视图覆盖,sass/放主题变量
  • resources/:静态资源,示例数据文件也常放这里
  • packages/:本地包,从外部引入的包会落在这里
  • build/:构建产物,按环境分目录
  • app.json、build.xml、index.html:项目配置与入口

app.json是整套构建的开关面板,改错一个字段构建就白跑。三段配置最常用:

{ "name": "MyApp", "framework": "ext", "toolkit": "classic", "builds": { "classic": { "toolkit": "classic", "theme": "theme-triton" } }, "output": { "base": "build", "appFolder": "app" }, "requires": [ "sencha-charts" ] }

toolkit决定用哪个工具包渲染;builds里的theme决定主题,换主题只改这一行;output.base是产物根目录,后面部署时服务器根目录要和它对上;requires里加sencha-charts是为了把图表包打进构建,classic 项目里不写这一行,图表相关类可能在构建阶段被裁掉。

3.4 三种构建环境与产物差异

构建命令是sencha app build [environment],三个环境差别不小:

# 开发环境:不做压缩,类按需加载,适合边改边看 sencha app build development # 测试环境:合并但保留可读性,方便定位问题 sencha app build testing # 生产环境:压缩混淆,只打包被引用到的类 sencha app build production # 开发期更常用的是 watch,起本地服务并监听文件变化 sencha app watch

development 产物的特点是类文件由引导文件按需拉取,改一行代码刷新页面就能看到效果,所以适合开发。testing 把所有类合并成一个文件、不做压缩,排查线上问题时读堆栈比 production 友好。production 会做类裁剪,没被任何视图或控制器引用到的类不会进包,这也是为什么上一节的requires必须写对——图表这类“只在运行时按名字创建”的东西,静态分析不一定能识别。

sencha app watch默认在 1841 端口起服务,访问http://localhost:1841即可。产物目录统一在build/<环境>/MyApp/下,production 部署时把这一层作为站点根目录,不要直接把项目根目录指过去。

4. 视图与组件落地:Ext.define、MVC 接线与 Grid/Chart/Form 三件套

前面都是铺路,这一章是真正写代码的部分。Ext JS 的写法和其他框架差别最大的地方是“声明式”和“类系统”:你几乎不写new,而是先注册类,再让容器按xtype去实例化。

4.1 Ext.define 与 Ext.create 的职责边界

这两个方法经常被混用。Ext.define只做注册,把类挂到命名空间上,不产生实例;Ext.create才是实例化。搞反了会导致类被重复定义,或者视图创建了两次。

// 注册类:只定义,不实例化,项目里 90% 的代码都是这一种 Ext.define('MyApp.view.main.Main', { extend: 'Ext.panel.Panel', xtype: 'app-main', // 别名,供容器按名字创建 title: '示例应用', layout: 'fit', items: [ { xtype: 'example-grid' } // 这里写 xtype,由容器延迟实例化 ] }); // 实例化:只有入口处或动态创建时才用 Ext.create('MyApp.view.main.Main', { renderTo: Ext.getBody() });

逻辑上,xtype是这套体系的粘合剂。控制器里按xtype查找组件、items里按xtype挂载、菜单点击后按xtype切换视图,全都依赖这层注册关系。参数上,extend决定继承链,xtype全局唯一,重名会让后注册的覆盖先注册的,症状是“样式和逻辑莫名其妙变成了另一个视图的”。

4.2 MVC 接线:Application、Controller、Store、View 各自的位置

Ext JS 支持 MVC 和 MVVM 两种组织方式。手册里的示例走 MVC:Application 声明有哪些视图、控制器、Store,Controller 用alias绑定到视图,View 通过controller字段声明归属。

Ext.define('MyApp.Application', { extend: 'Ext.app.Application', name: 'MyApp', // 这三组是"加载声明",不是实例化 stores: ['ExampleStore', 'ChartStore'], views: ['Main', 'GridView', 'ExampleChart', 'CaptureForm'], controllers: ['CaptureFormController'], launch: function () { // 应用启动后执行,适合做全局初始化 Ext.create('MyApp.view.main.Main', { renderTo: Ext.getBody() }); } });

stores、views、controllers三个数组的作用是告诉类加载器“这些类要进构建”,写漏了的表现是开发环境正常、production 构建后报 404 或者组件不显示。控制器与视图的绑定靠别名:

Ext.define('MyApp.view.form.CaptureFormController', { extend: 'Ext.app.ViewController', alias: 'controller.captureform', // 视图里用 controller: 'captureform' 绑定 onSubmit: function () { var form = this.getView().getForm(); if (!form.isValid()) { return; } form.submit({ url: 'api/capture', waitMsg: '提交中', success: function () { Ext.Msg.alert('结果', '保存成功'); }, failure: function () { Ext.Msg.alert('结果', '保存失败'); } }); } });

this.getView()在 ViewController 里拿到所属视图,这是 MVC 里最常用的一句。如果把控制器写成普通 Controller 而不是 ViewController,就没有getView(),只能靠Ext.ComponentQuery全局查,作用域容易串到别的视图上。

4.3 View 1:数据网格,从 Store 到列定义

网格是企业后台的主力组件,写它有固定的三步:先定 Store,再定列,最后挂过滤。

// 第一步:定义 Store,字段和代理都在这里 Ext.define('MyApp.store.ExampleStore', { extend: 'Ext.data.Store', alias: 'store.examplestore', fields: ['id', 'name', 'email', 'status'], proxy: { type: 'ajax', url: 'resources/data/users.json', reader: { type: 'json', rootProperty: 'items' // 告诉 Store 数据数组在返回 JSON 的哪一层 } }, autoLoad: true });

rootProperty是最常见的错配点:后端返回{ "items": [...] }就必须写items,返回裸数组就不写。写错的表现是请求 200、网格空白、控制台不报错。

// 第二步:定义网格视图,列定义与过滤器 Ext.define('MyApp.view.grid.GridView', { extend: 'Ext.grid.Panel', xtype: 'example-grid', title: '用户列表', requires: ['Ext.grid.filters.Filters'], store: { type: 'examplestore' }, columns: [ { text: 'ID', dataIndex: 'id', width: 60 }, { text: '姓名', dataIndex: 'name', flex: 1 }, { text: '邮箱', dataIndex: 'email', flex: 1 }, { text: '状态', dataIndex: 'status', width: 100, // renderer 三个参数分别是值、元数据、记录 renderer: function (value) { return value === 1 ? '启用' : '停用'; }, filter: { type: 'list', options: [[1, '启用'], [0, '停用']] } } ], plugins: 'gridfilters', tbar: [{ text: '刷新', handler: 'onRefresh' }] });

flex和width要分开用:固定宽度给width,剩余空间按比例分配给flex,全写width在小屏上会出现横向滚动条。renderer只影响显示,不改变 Store 里的值,做导出时拿到的仍是原始值。过滤器要同时具备三样东西才生效:列上的filter配置、plugins: 'gridfilters'、以及类上的requires,缺一个都表现为“过滤器箭头点不出来”。

4.4 View 2:柱状图,先确认图表包进了构建

图表在 classic 里属于可选包,没引入时组件会静默不渲染,比报错更难查。数据先备好:

Ext.define('MyApp.store.ChartStore', { extend: 'Ext.data.Store', alias: 'store.chartstore', fields: ['month', 'value'], data: [ { month: '1月', value: 42 }, { month: '2月', value: 58 }, { month: '3月', value: 65 }, { month: '4月', value: 51 } ] });

视图侧的关键是坐标轴和系列的字段绑定:

Ext.define('MyApp.view.chart.ExampleChart', { extend: 'Ext.chart.CartesianChart', xtype: 'example-chart', requires: [ 'Ext.chart.series.Bar', 'Ext.chart.axis.Category', 'Ext.chart.axis.Numeric' ], store: { type: 'chartstore' }, axes: [ { type: 'category', position: 'bottom', fields: ['month'] }, { type: 'numeric', position: 'left', fields: ['value'], minimum: 0 } ], series: [{ type: 'bar', xField: 'month', yField: 'value', highlight: true, label: { display: 'insideEnd', field: 'value' } }] });

axes里的fields和series里的xField/yField必须和 Store 的字段名一字不差,大小写也算。改动后端字段名之后忘了同步这里,表现是图表容器渲染出来了但一根柱子都没有。minimum: 0建议显式写,不然数值差异小时纵轴会自动裁到数据区间,视觉上放大差异。图表放进layout: 'fit'的容器里,否则容器高度为 0,同样是“渲染成功但看不见”。

4.5 View 3:表单捕获,字段、校验与提交分支

表单的价值在提交链路,字段定义反而是最简单的部分。

Ext.define('MyApp.view.form.CaptureForm', { extend: 'Ext.form.Panel', xtype: 'capture-form', title: '信息登记', bodyPadding: 16, controller: 'captureform', // 绑定控制器别名 defaultType: 'textfield', defaults: { anchor: '100%', labelWidth: 80, allowBlank: false }, items: [ { fieldLabel: '姓名', name: 'name' }, { fieldLabel: '邮箱', name: 'email', vtype: 'email' }, { fieldLabel: '年龄', name: 'age', xtype: 'numberfield', minValue: 0, maxValue: 120 }, { fieldLabel: '备注', name: 'remark', xtype: 'textareafield', allowBlank: true } ], buttons: [ { text: '重置', handler: 'onReset' }, { text: '提交', formBind: true, handler: 'onSubmit' } ] });

defaults是批量配置,四个字段共用;allowBlank: false让必填校验自动生效;vtype: 'email'走内置校验规则,不用自己写正则;formBind: true让按钮跟随表单有效性自动禁用,省掉一堆手动判断。name字段决定提交时的参数名,和后端约定不一致就会收到空对象。

提交的成功判定有个容易被忽略的约定:Ext JS 只看响应 JSON 里的success字段,返回{ "success": false }会走failure分支,哪怕 HTTP 状态码是 200。后端如果只返回{ "code": 0 }这种结构,前端永远走失败分支,这个坑我在不止一个项目里见过。

5. 避坑与排查:五个把开发卡住的现场

这些是走一遍完整流程必然会撞上的几个点,按“现象 → 原因 → 解决”记下来,下次遇到能省半小时。

5.1 生成完项目双击 index.html 一片白

现象:项目生成成功,sencha app build development也不报错,直接双击index.html打开是白屏,控制台一堆资源加载失败。

原因:开发模式下页面靠引导文件在运行时按需拉取类文件,走的是 XHR。file://协议下这些请求被浏览器安全策略挡掉,类加载器拿不到任何类,应用自然起不来。

解决:一律通过本地服务访问。用sencha app watch起服务后访问http://localhost:1841,或者用任意静态服务器指向项目根目录。不要养成双击打开的习惯,这个动作在 Ext JS 项目里没有意义。

5.2 production 构建成功,部署后资源全 404

现象:sencha app build production输出成功,把整个项目目录传到服务器,页面能开但样式、图片、接口全挂。

原因:产物在build/production/MyApp/这一层,而不是项目根目录。服务器根目录指错一层,app.js和resources/的相对路径就全断了。

解决:站点根目录指向build/production/MyApp/。如果部署路径有前缀(比如挂在某个子路径下),同步改app.json里的output.base和构建参数,别在前端代码里硬编码路径。

5.3 图表容器渲染了但一根柱子都没有

现象:图表区域占位正常,坐标轴也出来了,就是没有数据图形,控制台无报错。

原因:三类问题最常见——Store 字段名与xField/yField不一致;app.json的requires里没加sencha-charts;容器没设布局导致高度为 0。

解决:先看 Store 的getCount()是不是 0,确认数据到了;再核对字段名大小写;然后在app.json补requires重新构建;最后检查父容器有没有layout: 'fit'。按这个顺序查,基本两轮内能定位。

5.4 表单提交后端收到了,前端却提示失败

现象:后端日志显示数据已入库,前端弹的是失败提示。

原因:form.submit的成功判定只看响应体里的success字段,HTTP 200 不代表成功。后端返回自定义结构(如{ "code": 0, "msg": "ok" })时,前端读不到success,走failure回调。

解决:要么让后端在响应里补success: true,要么在前端用Ext.Ajax.request自己发请求、自己判断业务码,不走form.submit的默认判定。团队里约定一种,别两套混用。

5.5 换台机器构建报 Sass 编译错误

现象:同一份代码,同事机器构建正常,新机器上sencha app build在样式编译阶段中断,报找不到某个变量或路径错误。

原因:Cmd 版本或 JDK 版本不一致;或者项目路径里带空格、中文,Sass 编译器的路径解析在这类目录下容易出问题。

解决:先用sencha which和java -version对齐两边环境,把 Cmd 版本写进项目文档;再把项目挪到纯英文无空格路径下重试。团队统一用 npm 方式管理 Cmd,这类问题基本能绝迹。

6. 上线前验收:把构建产物和组件行为各验一遍

production 构建最容易骗人的地方是“构建成功”四个字,类被裁掉、资源没进包,它一样返回成功。我习惯在出包后跑两个检查,一个查产物结构,一个查关键组件有没有被打进 bundle。

# 产物结构自检:关键文件是否齐、是否残留开发期引导文件 APP=MyApp OUT=build/production/$APP for f in index.html app.js resources; do [ -e "$OUT/$f" ] && echo "OK $f" || echo "MISS $f" done # 产物里不应再引用开发期的引导配置 grep -R "bootstrap.json" "$OUT" && echo "警告:产物仍引用开发引导" || echo "OK 未引用"

第一段循环检查三样东西:入口页、合并后的脚本、静态资源目录。第二段是后悔药——development 构建依赖bootstrap.json做动态类加载,production 不该出现它,如果产物里还残留引用,说明构建环境没切对,部署上去大概率白屏。

第二道检查针对组件裁剪。production 会做类裁剪,没被静态引用的类不进包,而 xtype 是字符串字面量,压缩后仍然保留,可以据此反查:

// tools/check-bundle.js —— 确认关键 xtype 进了生产包 const fs = require('fs'); const path = require('path'); const bundle = path.join('build', 'production', 'MyApp', 'app.js'); const code = fs.readFileSync(bundle, 'utf8'); // 这三个 xtype 分别对应网格、图表、表单视图 ['example-grid', 'example-chart', 'capture-form'].forEach(function (xtype) { console.log(code.includes(xtype) ? 'OK ' + xtype : 'MISS ' + xtype); });

这段脚本读的是合并后的app.js,逐个比对 xtype 字符串。出现MISS通常意味着对应的视图类没被Application的views数组声明,或者只被动态字符串引用、静态分析没识别到。补进views数组或者在该类的requires里显式声明依赖,重新构建即可。

顺手再补一个运行时验证的习惯:在浏览器控制台用Ext.ComponentQuery.query('example-grid')查组件是否存在,用Ext.ComponentQuery.query('example-grid')[0].getStore().getCount()看数据条数。构建产物里的类名被压缩了,但 xtype 不变,这两个查询在生产包里一样能用,比翻源码快得多。

最后说个血泪经验。我早期出包只信构建日志的绿色输出,直到有一次图表包被裁掉、线上整整一个面板空白了两天才被发现。从那以后我每次出包都强制跑一遍上面这两个检查,再谈部署。希望帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进