
当涉及到Ext JS库时,专家们听到了很多消极的东西:笨重,昂贵,越野车。 通常,大多数问题与无法烹饪有关。 使用Sencha Cmd和所有css正确组装的项目,图像在1Mb区域中的生产权重,与同一个Angular相当。 是的,而且故障不多...
Sencha的想法很不同,但是即使是有原则的反对者也承认,很难找到构建严肃的Intranet项目的最佳解决方案。
我认为,Ext JS中最有价值的东西不是UI组件的集合,而是一个好的OOP体系结构。 即使考虑到近年来JS的快速发展,本机类中仍缺少7年前在Ext JS中实现的许多必需的东西(命名空间,mixins,静态属性,方便的父方法调用)。 这就是几年前促使我尝试在后端启动Ext JS类的原因。 关于最初的类似实验,我已经在Habré上发表过文章。 本文介绍了旧思想的新实现和许多新思想。
在开始之前,请注意以下问题:您认为如何,它在哪里执行,下面的代码段是做什么的?
Ext.define('Module.message.model.Message', { .... ,async newMessage() { ......... this.fireEvent('newmessage', data); ...... } ... })
此代码在服务器上执行,并在连接到服务器的所有客户端计算机上的“ Module.message.model.Message”类的所有实例中导致“ newmessage”事件。
为了说明使用服务器端Ext JS的可能性,我们将分析一个简单的聊天项目。 仅当您输入用户输入昵称时,我们才会进行任何登录。 您可以发布常规或私人消息。 聊天应该实时进行。 那些希望的人可以立即尝试所有这种经济活动。
安装方式
首先,我们需要nodejs 9+和redis-server(假定它们已经安装)。
git clone https://github.com/Kolbaskin/extjs-backend-example cd extjs-backend-example npm i
我们启动服务器:
node server
在浏览器中,打开
localhost页面:3000 / www / auth /
输入一些昵称,然后按Enter。
该项目是一个演示,因此不支持旧的浏览器(有ES8设计),请使用新的Chrome或FF。
伺服器
让我们去吧。
服务器代码(server.js)
如您所见,这里的所有内容或多或少都是服务器上的标准。 有趣的是包含Ext JS类以服务相应的路由:
app.use('/api/auth', Ext.create('Api.auth.Main')); app.use('/www/auth', Ext.create('Www.login.controller.Login'));
REST API实施
Api.auth.Main类将请求提供给REST API(受保护的/ rest / auth / Main.js)。
Ext.define('Api.auth.Main', { extend: 'Api.Base',
使用服务器上的XTemplate生成HTML页面
第二类是Www.login.controller.Login,它使用登录表单(受保护的/www/login/controller/Login.js)构建一个常规的html页面。
Ext.define('Www.login.controller.Login', {
模板使用标准的XTemplate(受保护的/www/login/view/login.tpl)
<h2>{pageTitle} (date: {[Ext.Date.format(values.date,'dmY')]})</h2> <form method="post"> <input name="name" placeholder="name"> <button type="submit">enter</button> </form>
一丝不苟的读者会说,上述所有内容都是一个完全标准的集合,因此,无需通过将Ext JS传输到服务器来围堵这个花园。 因此,我们继续进行本文的第二部分,它将显示其全部意图。
顾客
让我们在静态目录中创建普通的客户端Ext JS应用程序。 在此示例中,我故意不考虑使用cmd,而是采用了已经构建的ext-all和standard主题。 大会问题是一个单独的主题,也许会另辟一个职位。
一切都始于app.js
Web套接字的存在是至关重要的一点,正是它可以让您实现下面描述的所有魔术。
页面上元素的布局包含在Admin.view.Viewport类中(静态/app/view/Viewport.js)。 那里没什么有趣的。
主要功能元素(用户列表,消息栏和发送表单)被实现为单独的模块。
用户清单
该列表的简单算法如下:打开页面时,当前用户已从服务器加载。 当新用户连接时,服务器在“ Module.users.model.UserModel”类中生成一个“ add”事件,当断开连接时,在同一类中,将引发“ remove”事件。 事实是该事件是在服务器端触发的,您可以在客户端上对其进行跟踪。
现在,首先是第一件事。 在客户端,存储杂项数据(静态/应用程序/模块/用户/商店/UsersStore.js)
Ext.define('Module.users.store.UsersStore', { extend: 'Ext.data.Store' ,autoLoad: true ,total: 0 ,constructor() {
有两个有趣的观点。 首先,在“ const data = await this.dataModel。$ Read();”行中 该模型的服务器方法被调用。 现在,您无需使用Ajax,支持协议等,只需将服务器方法称为本地方法即可。 同时,安全性也没有牺牲(更多内容见下文)。
其次,this.dataModel.on(...)的标准构造使您可以跟踪服务器将生成的事件。
该模型是应用程序的客户端和服务器部分之间的桥梁。 就像光的二元论一样-它实现了前端和后端的属性。 让我们仔细看一下模型。
Ext.define('Module.users.model.UserModel', { extend: 'Core.data.DataModel' ,testClientMethod() { ... } ,testGlobalMethod() { ... } ,privateServerMethod() { .... } ,async $read(params) {
请注意以下注释:/ *作用域:服务器* /和/ *作用域:客户端* /-这些构造是服务器的标签,通过它确定方法的类型。
testClientMethod-此方法专门在客户端上运行,并且仅在客户端可用。
testGlobalMethod-此方法在客户端和服务器上运行,并且可以在客户端和服务器端使用。
privateServerMethod-该方法在服务器上执行,仅可用于在服务器上调用。
$ read是仅在服务器端运行的最有趣的方法类型,但是您可以在客户端和服务器上调用它。 $前缀使客户端可以使用任何服务器端方法。
您可以使用Web套接字跟踪客户端连接和断开连接。 为每个用户连接创建一个Base.wsClient类的实例(protected / base / wsClient.js)
Ext.define('Base.wsClient', { extend: 'Core.WsClient'
与标准方法不同,fireEvent方法具有一个附加参数,在该参数上传递事件应在哪个客户端上触发。 传递单个客户端标识符,标识符数组或字符串“ all”是可以接受的。 在后一种情况下,事件将在所有连接的客户端上触发。 否则,这是标准的fireEvent。
发送和接收消息
表单控制器(静态/app/admin/modules/messages/view/FormController.js)负责发送消息。
Ext.define('Module.messages.view.FormController', { extend: 'Ext.app.ViewController' ,init(view) { this.view = view;
该消息未保存在服务器上的任何位置,只需触发“ newmessage”事件。 有趣的是调用“ this.fireEvent('newmessage',data.to,msg);”,其中客户端标识符作为消息接收者传递。 因此,实现了私人消息的分发(静态/ app / admin /模块/消息/模型/Model.js)。
Ext.define('Module.messages.model.Model', { extend: 'Core.data.DataModel' ,async $newmessage(data) { const msg = { user: data.user, message: data.message } if(data.to && Ext.isArray(data.to) && data.to.length) { this.fireEvent('newmessage', data.to, msg); } else { this.fireEvent('newmessage', 'all', msg); } return true; } })
与用户一样,消息列表的数据由商店驱动(静态/ app / admin /模块/消息/商店/MessagesStore.js)
Ext.define('Module.messages.store.MessagesStore', { extend: 'Ext.data.Store', fields: ['user', 'message'], constructor() {
通常,在此示例中,这就是所有有趣的事情。
可能的问题
客户端上服务器方法的可用性当然很好,但是安全性又如何呢? 事实证明,邪恶的黑客可以看到服务器代码并尝试破解后端?不,他不会成功。 首先,所有服务器方法在发送到客户端浏览器时都将从类代码中删除。 为此,打算使用注释/指令/ *作用域:... * /。 其次,最公共的服务器端方法的代码被中间结构替代,该中间结构在客户端实现了远程调用机制。
再次谈到安全性。 事实证明,如果可以在客户端上调用服务器方法,我可以调用任何此类方法吗? 如果这是数据库清理方法?从客户端,您只能调用名称中带有$前缀的方法。 对于这种方法,您自己确定检查和访问的逻辑。 如果没有$,外部用户将无法访问服务器方法,他甚至都不会看到它们(请参阅前面的答案)
看起来您拥有一个完整的系统,其中客户端和服务器之间有着千丝万缕的联系。 水平缩放是否可能?实际上,该系统看起来是整体的,但事实并非如此。 客户端和服务器可以“驻留”在不同的计算机上。 客户端可以在任何第三方Web服务器(Nginx,Apache等)上运行。 自动项目构建器很容易解决客户端与服务器分离的问题(我可以为此撰写单独的文章)。 为了实现内部服务消息传递机制,系统使用队列(即,为此需要Redis)。 因此,只需添加新机器即可轻松地水平扩展服务器部分。
通常,使用通常的开发方法,后端提供了一组API,您可以将它们与各种客户端应用程序(网站,移动应用程序)连接。 在您的情况下,事实证明只有用Ext JS编写的客户端可以与后端一起使用吗?在服务器上,尤其是在模块模型中,实现了某种业务逻辑。 为了通过REST API提供对它的访问,一个小的“包装器”就足够了。 本文的第一部分提供了一个相应的示例。
结论
如您所见,为了对相当复杂的应用程序进行舒适的编码,很可能在前端和后端使用一个库。 这具有明显的好处。
加快开发过程。 每个团队成员都可以在后端和前端工作。 由于“我正在等待此API出现在服务器上”原因而导致的停机时间不再相关。
更少的代码。 可以在客户端和服务器上使用相同的代码部分(检查,验证等)。
维护这样的系统更加简单和便宜。 该系统将能够支持一个(或两个相同但可互换),而不是两个不同的程序员。 出于同样的原因,与团队更替相关的风险也较低。
从包装盒中创建实时系统的能力。对后端和前端使用单个测试系统。