本篇目录

  • 前言
  • 正文
    • 通过路由控制应用
    • 路由可以做什么
    • 路由不可以做什么
    • 什么是 Hash?
    • 在你的应用中实现路由
    • 更新 Hash
    • 默认 Token
    • 带参数的 Hash
    • Hash 参数格式化
    • Route 处理
    • 处理不匹配的 Route
    • 对一个 Hash 使用多条 route
    • 自动 Hashbang 支持
    • 暂停和继续路由
    • 使用通配符
    • 全局事件
    • Action 类
    • 无 MVC/MVVM 情况下的路由

前言

本篇翻译自 Ext JS 6.7.0 官方文档,原文地址如下:

https://docs.sencha.com/extjs/6.7.0/guides/application_architecture/router.html

文档主要讲解了 Ext 提供的路由功能,之所以把它翻译出来,是因为大多数前端路由都采用了相近的模式,通过本文可以更好地理解单页面应用路由功能的原理及其使用方法。

正文

通过路由控制应用

在一个正常的网页中,用户通过点击链接或填写表单跳转至不同的页面。然而,在单页面应用中,用户的交互操作并不会导致加载新的页面,而是被一个页面和一些组件响应。那么,该如何让用户使用浏览器的前进和后退按钮?答案是使用 Ext JS 提供的路由功能记录 URL 的 hash 值变化。

路由可以做什么

路由可以通过浏览器的历史记录(history stack)追踪应用的状态,同时也可以实现深度链接到你的应用的某个部分。用户可以把这个部分的地址添加到书签,并直接分享给他人。

这里的历史记录是指浏览器所记录的一些信息,而我们通常说的历史记录是对这种信息的封装展示。为了表示区分,下文将使用 history stack 来进行讨论。

路由不可以做什么

路由不可以被用来存储数据或会话。数据应该被存储在持久化的数据源,比如 cookie 或 localstorage 中。路由只是一种一种追踪应用状态的方式。

什么是 Hash?

浏览器通过 URI 在互联网上导航。URI 由多个部分组成,让我们看一个例子:

https://www.example.com/apps/users#user=1234

这一定看起来很熟悉。然而,你可能不认识这个 #user=1234。这个部分叫做 “hash” 或者 “片段标识符” 。想了解更多关于 hash 的信息,请阅读以下资源:

http://en.wikipedia.org/wiki/Fragment_identifier

hash 为应用提供了一种不必重新加载页面的控制浏览器 history stack 的方法。当 hash 变化时,浏览器会把整个 URI 加入到 history stack 中,这样就可以利用浏览器的 前进/后退 按钮在包括 hash 值改变的 URI 中跳转。

举个例子,当你把上面的 URI 更新如下后会发生什么?

https://www.example.com/apps/users#user/5678

浏览器触发了一个名为 hashchange 的事件,我们可以在应用中利用它。用户可以点击返回按钮返回 #user=1234 的 hash,同样会触发事件,你可以在你的应用中响应它。重要的一点是,hash 不会被送至服务器端。hash 通常只用于记录客户端的 URI 表现。Ext JS 的路由功能性地依靠浏览器的 hash 去实现应用状态追踪和深度链接。

在你的应用中实现路由

Router 类的目的是使一个 MVC 应用中的 hash 改变更容易理解。你可以使用 Ext.util.History 去响应 hash 变化。并且,Ext.util.History 提供了更手动的过程,需要一个恰当的 Router 类。该类提供了一个简单的整合到 Ext JS 5 MVC 应用的方法,只需要在 view controller 中定义几个路由即可。一条路由是指一个字符串,对应 hash 值。下面是一个通过 controller 实现路由的简单例子:

Ext.define('MyApp.view.main.MainController', {extend : 'Ext.app.ViewController',routes : {'users' : 'onUsers'},onUsers : function () {//...}
});

这个路由会响应 #users 这个 hash并执行 onUsers 方法(仅在该 controller 的作用范围内有效),可以看到,routes 是作为 config 对象而不是根出现的,所以其作用域可以被很好的控制。

更新 Hash

为了更新 hash,controller 提供了 redirectTo 方法:

this.redirectTo('user/1234');

这将会把 hash 变为 #user/1234,可以识别这个 hash 的 routes 配置将会被执行。

redirectTo 方法还可以接收一个可选参数(options)。选择如下:

  • force ——该参数如果为 true,路由配置将强制被执行,即使地址栏中的 hash 与传递给 redirectTo 的 hash 相同。
  • replace——默认地,hash 会被改变,而浏览器的历史记录会新增一条,这意味着浏览器的后退按钮可以返回之前的 hash。如果该参数为 true,当前的浏览器历史记录会被传递给 redirectTo 的 hash 取代。(也就是无法返回)。

注意: 为了向下兼容,该 options 可以是一个 boolean,在这种情况下设置的是 force 参数。

默认 Token

当一个应用启动时,可能被配置了一个默认的 hash 值。比如,你可能需要在没有 hash 存在时默认使用 #home,可以在 /app/view/Application.js 中使用 defaultToken 配置:

Ext.define('MyApp.Application', {extend : 'Ext.app.Application',//...defaultToken : 'home'
});

当应用启动时,它会检测当前 URI 的 hash,如果不存在,则会自动添加 #ome 并执行相应的 handlers。

带参数的 Hash

应用可能会在 hash 中附带参数。比如 userID,在教程中我们提到过用 #uesr/1234 作为 hash,在这种情况下,我们可能想要 1234 作为 id 参数。你需要设置你的 controller 去响应 #user/1234 hash:

Ext.define('MyApp.view.main.MainController', {extend : 'Ext.app.ViewController',routes : {'user/:id' : 'onUser'},onUser : function (id) {//...}
});

我们配置的 route 是 #user/:id,这个冒号,代表这里有一个参数,你的应用将会把这个参数传递给 onUser 方法。该方法可以按照与 route 中定义的相同的顺序接收到相同数量的参数。

Hash 参数格式化

应用可能想要为 user ID 强制执行某种格式。在之前的探索中 ID 一直是数字,我们可以在 route 中把它配置为一个对象:

Ext.define('MyApp.view.main.MainController', {extend : 'Ext.app.ViewController',routes : {'user/:id' : {action     : 'onUser',conditions : {':id' : '([0-9]+)'}}},onUser : function (id) {//...}
});

接着看这个例子,首先 onUser 方法被移动到了 action 中,其效果和之前相同。接着我们通过 conditions 这个配置提供对象参数。我们想控制的是带着冒号的那个参数,同时我们使用了正则表达式(字符串)。比如对 :id,我们使用了 ([0-9])+,意思是允许 0 到 9 之间的所有数字组成任意长度。我们使用正则表达式字符串是因为正则表达式对象会匹配整个 hash。如果存在多参数我们必须把字符串结合为一个正则表达式对象。如果你没有为一个参数提供 condition,它默认为:

([%a-zA-Z0-9\\-\\_\\s,]+)

Route 处理

这里的 Route 是指 controller 中的 config,而使用中文“路由”表述的是指 Ext 提供的路由功能,或者说 Ext 的路由器,即 Router。

有时应用会需要阻止路由的处理。比如我们想检查当前用户是否具有访问应用某一部分的权限。可以使用 before 来停止当前路由、停止所有路由或继续执行路由操作。

下面的例子会继续执行路由操作:

Ext.define('MyApp.view.main.MainController', {extend : 'Ext.app.ViewController',routes : {'user/:id' : {before  : 'onBeforeUser',action  : 'onUser'}},onBeforeUser : function (id, action) {Ext.Ajax.request({url     : '/security/user/' + id,success : function() {action.resume();}});},onUser : function (id) {//...}
});

onBeforeUser 方法中,:id 作为一个实参传递,而第二个参数是 action。如果执行了 actionresume 方法,路由会继续执行, onUser 方法会被调用。注意我们可以等待 AJAX 请求完成后再继续执行路由操作。

我们可以通过执行 stop 方法来让应用停止执行当前的路由:

Ext.define('MyApp.view.main.MainController', {extend : 'Ext.app.ViewController',routes : {'user/:id' : {before  : 'onBeforeUser',action  : 'onUser'}},onBeforeUser : function (id, action) {Ext.Ajax.request({url     : '/security/user/' + id,success : function () {action.resume();},failure : function () {action.stop();}});},onUser : function (id) {//...}
});

before 本身同样支持许可。除了执行 action 的 resumestop 方法外,你也可以使用 resolvereject 达到同样的效果:

Ext.define('MyApp.view.main.MainController', {extend : 'Ext.app.ViewController',routes : {'user/:id' : {before  : 'onBeforeUser',action  : 'onUser'}},onBeforeUser : function (id) {return new Ext.Promise(function (resolve, reject) {Ext.Ajax.request({url     : '/security/user/' + id,success : function () {resolve()},failure : function () {reject();}});});},onUser : function (id) {//...}
});

注意: 你可以选择是否使用 action 作为参数,但问题必须要被解决。要么执行 action 的 resume 或 stop,要么 promise 被 resolved 或 rejected。

处理不匹配的 Route

如果 hash 被改变但没有匹配的 route,路由将什么都不做,只是触发 unmatchedroute 事件,可以在 Ext.application 中监听:

Ext.application({name : 'MyApp',listen : {controller : {'#' : {unmatchedroute : 'onUnmatchedRoute'}}},onUnmatchedRoute : function (hash) {//...}
});

路由还会触发一个全局事件:

Ext.application({name : 'MyApp',listen : {global : {unmatchedroute : 'onUnmatchedRoute'}},onUnmatchedRoute : function (hash) {//...}
});

全局事件也可以直接在 Ext 命名空间中监听:

Ext.on('unmatchedroute', function (hash) {//...
});

对一个 Hash 使用多条 route

Ext JS 应用可能比较复杂,有时我们需要对一个 hash 进行多条 route 配置。路由提供了这种功能所以我们不需要在 controller 中进行额外的配置,你只需要在 hash 中使用 | 来分割,比如:

#user/1234|messages

在这种情况下,我们想要展示 id 为 1234 的用户的详细信息,并且附带一些 message。hash 中对应的两个 route 配置都会被执行。他们相互之间会被隔离(sandboxed),也就是说,如果停止了 user/1234 的 route,message route 仍会继续执行。一个比较重要的点是每个 route 的执行顺序和 hash 中所写的顺序一致。user/1234 的 route 会比 message 的 route 先执行。

你可以通过改变 Ext.route.Router.multipleToken 控制分割符。

为了处理多个 route,redirectTo 方法也可以接收一个对象作为参数。这样你就可以操作(更新、移除) hash 的某一部分。假设我们有 3 个不同的 route(可以在不同的 controller 中):

routes: {'bar' : 'onBar','baz' : {action : 'onBaz',name   : 'bazRoute'},'foo' : 'onFoo'
}

baz route 有一个新配置叫 name。通过这个配置我们可以更好地引用 route。默认地,name 属性是 url 传递给 routes 的 key;barfoo 是其它 route 的名称,我们现在有 barbazfoo 作为三个 route 的名称。

可以通过如下方法传递一个字符串初始化 hash:

this.redirectTo('foo');

现在 hash 变成了 #foo,我们可以使用 multipleToken 属性传递被分割开的 token 所组成的字符串,就像我们过去做的那样。或者可以传递一个对象:

this.redirectTo({bar : 'bar'
});

现在 hash 变成了 #foo|bar 。我们以 #foo 作为最初的 hash,然后向 redirectTo 传递了一个对象,并且传递了 bar 作为 key,这个 key 作为 route 的 name 被我们引用。对于复杂的 url 来说,使用 name 属性是一个很好的选择。

如果该 key 对应的 route 在当前 hash 中存在,但跳转的目标,即 value 是虚值,则该 route 会被移除。如果不是虚值,则会跳转到新的目标。如过 key 对应的 route 不存在,则它会被添加到 hash 中。如果我们想要替换掉 foo,可以传递一个 value 给它:

this.redirectTo({foo : 'foober'
});

现在 hash 编程了 #foober|bar ,注意 hash 中各个 token 的顺序被保留了。下面是一个更复杂的例子:

this.redirectTo({bar      : 'barber',bazRoute : 'baz',foo      : null
});

在这个例子中,我们移除了一个、更新了一个、添加了一个,hash 现在变成了 #barber|baz。通过传递对象,你可以更好的控制 hash 的变化,不用担心目前的 hash 是什么样子,Ext JS 会帮你解决。

自动 Hashbang 支持

如果你的应用需要 hashbang 而非常规 hash,Ext JS 现在可以为你自动化处理。Ext.util.History 是用于从浏览器中获取或设置 hash 的类,利用它可以自动地将 hash 变为 hashbang,只需要配置一个简单的属性:

Ext.util.History.hashbang = true;

这样就可以了,你的 routes 配置依然如下:

routes : {'foo' : 'onFoo'
}

redirectTo 的使用方法也没变:

this.redirectTo('foobar');this.redirectTo({foo : 'foobar'
});

Ext.util.History 自动地把 hash 变成了 #!foobar。你也可以通过在 application 中使用一个新的配置,即 router 来实现这个功能:

Ext.application({name : 'MyApp',router : {hashbang : true}
});

也可以对路由使用 setter 方法:

Ext.route.Router.setHashbang(true);

注意: hashbang 也是 hash,只是在 # 后面加了一个 !,也就是说它们不能混合或匹配,只能二选其一。

暂停和继续路由

如果你需要让路由等待一些进程的完成,而不去响应 hash 的变化,你可以暂停(suspend)路由。一旦暂停,hash 的变化会被加入到队列,并在继续(resume)后执行。一个常见的场景是在应用启动时延迟路由,以等待 store 或用户 session 的检查。下面是一个例子:

Ext.application({name : 'MyApp',defaultToken : 'home',launch : function () {var me = this;Ext.route.Router.suspend();me.checkUserSession().then(me.onUser.bind(me), me.onUserError.bind(me));},onUser : function (user) {MyApp.$user = user;Ext.route.Router.resume();},onUserError : function (error) {// handle error// do not execute queued hash changesExt.route.Router.resume(true);this.redirectTo('login');}
});

如果你确定不想把 hash 的变化加入队列中以待后续处理,可以把 suspend 附带的参数设置为 false:

Ext.route.Router.suspend(false);

使用通配符

如果你想在所有配置的 route 前处理一个 route,你可以使用通配符(wildcard) route。

routes : {'*'   : 'onRoute','foo' : 'onFoo'
}routes : {'*'   : {before : 'onBeforeRoute',action : 'onRoute'},'foo' : 'onFoo'
}

通配符 route 会先于 foo 执行。

全局事件

Ext JS 提供了几个全局事件。这些事件被 Ext 命名空间触发并且可以在 Ext 或全局事件域中监听。下面是这些事件:

  • beforeroutes 在所有 route 被执行前触发。返回 false 可以取消 route 的执行。
  • beforeroute 在某个 route 被执行前触发。返回 false 可以取消该 route 的执行。
  • unmatchedroutes 在 route 未被 token 匹配(即输入了假地址)时触发。

之前提到,有两种方法可以监听这些事件,推荐的方法是通过 controller/viewcontroller 中的全局事件域监听:

listen : {global : {beforeroutes : 'onBeforeRoutes'}
},onBeforeRoutes : function (action, tokens) {return tokens.length === 1;
}

或者可以通过 Ext 命名空间:

 Ext.on('unmatchedroute', function (token) {Ext.Msg.alert('Unmatched Route', '"' + token + '" was not matched!');});

Action 类

before action 中,你看见了 action 参数。它是 Ext.route.Action 的实例,并且不仅仅提供继续或停止动作的功能。这个类实际上可以通过使用 beforeaction 动作变得更加动态,这两个动作甚至可以在动作执行期间使用:

onBeforeUser : function (id, action) {return new Ext.Promise(function (resolve, reject) {action.before(function (id, action) {action.action(function (id) {//...});action.resume();});Ext.Ajax.request({url     : '/security/user/' + id,success : function () {resolve()},failure : function () {reject();}});});
}

在这个例子中,我们往执行中的 before 动作列表中添加了 before 动作(它会被最后执行)。在这个 before 动作中,我们同样往 action 动作列表中添加了一个 action 动作。注意 id 参数仍然要传递给新的 action。

如果你想知道 action 什么时候被完全执行或拒绝(所有的 before 和所有的 action),action 类有一个 then 方法:

onBeforeUser : function (id, action) {var me = this;return new Ext.Promise(function (resolve, reject) {action.then(Ext.bind(me.onFinish, me), Ext.bind(me.onRejection, me));//...});
}

如果你想停止 beforeaction 的执行,这里同样有一个 stop 方法:

onBeforeUser : function (id, action) {try {this.foo();} catch (e) {action.stop();}
}

无 MVC/MVVM 情况下的路由

Ext JS 6.5.0 创建了一个新的 mixin,这样 routes 就可以在所有类中被配置了。

Ext.define('MyClass', {mixins : ['Ext.route.Mixin'],routes : {'foo' : 'onFoo'},constructor : function (config) {this.initConfig(config);},doSomething : function () {this.redirectTo('foo');},onFoo : function () {//...}
});

Ext JS 6.7 中文文档:路由的使用相关推荐

  1. Ext JS 6.7 中文文档:应用架构介绍(MVC/MVVM)

    最近为了做内网的后台学了下 Ext JS,觉得挺有意思的,顺手翻译一下文档,主要是架构部分和核心概念部分. 这个文档是我目前看到的最好的关于 MVC.MVVM 的解释与探讨,十分值得参考. 本篇目录 ...

  2. Backbone.js(1.1.2) API中文文档

    2019独角兽企业重金招聘Python工程师标准>>> Backbone.Events(事件) Events 是一个可以融合到任何对象的模块, 给予 对象绑定和触发自定义事件的能力. ...

  3. App.js实现使用js开发app的应用,此文是中文文档

    在阅读前,在此说明下,本人英文一直不好,所以该文档是借助翻译工具翻译的,阅读起来可能有点不好,请各位谅解,哪位大神有标准的中文文档请分享下 Github下载地址:https://github.com/ ...

  4. Underscore.js Version (1.2.3) 中文文档

    Underscore 一个非常实用的JavaScript库,提供许多编程功能的支持,就像你期望 Prototype.js (或者 Ruby), 有这些功能且不扩展任何JavaScript的原生对象.有 ...

  5. c if sortable html,sortable.js中文文档

    sortable.js中文文档 Sortable.js是一款优秀的js拖拽库,支持ie9及以上版本ie浏览器和现代浏览器,也可以运行在移动触摸设备中.不依赖jQuery.支持 Meteor.Angul ...

  6. three.js中文文档下载_threejs基础学习一

    three.js是个插件库,就是使用javascript 来写3D程序.网上教程挺多的,官方的中文文档也有,甚至有本书<Three.js开发指南>,所以这个插件库还是很成熟的.没错,最近一 ...

  7. 以太坊智能合约开发,Web3.js API 中文文档 ethereum web3.js入门说明

    以太坊智能合约开发,Web3.js API 中文文档 ethereum web3.js入门说明 为了让你的Ðapp运行上以太坊,一种选择是使用web3.js library提供的web3.对象.底层实 ...

  8. Chart.js中文文档-雷达图

    雷达图或蛛网图(Radar chart) 简介 A radar chart is a way of showing multiple data points and the variation bet ...

  9. Chart.js 中文文档(整理)

    Chart.js 中文文档(整理) Chart.js是一个简单.面向对象.为设计者和开发者准备的图表绘制工具库. 步骤: 1.在HTML中引入Chart.js文件 <script src=&qu ...

最新文章

  1. sd.js 2.0封装:更加简化请求传参内容
  2. php admin允许空密码登陆
  3. python练习2 购物车程序
  4. c语言平滑raw图像(取平均值法)
  5. 面向过程和面向对象编程的优缺点
  6. Linux 配置jdk
  7. Shiro结合redis的统一会话管理:自定义会话管理器
  8. c3p0配置 initialPoolSize 和minPoolSize 可以设为0吗?设0有坏处吗?
  9. python基础一 day17 二分查找算法
  10. mac上设置新版chrome浏览器跨域
  11. 9个实用PHP函数和功能
  12. EditPlus软件的安装与配置
  13. STM32智能小车电路综合实习报告
  14. LOL各大服务器所在位置,LOL各大服务器所在地,8个大区全都在广东,是其他省的两倍...
  15. kinect2.0 之摄像头
  16. 小猫钓鱼java代码,【Java个人笔记】练习-小猫钓鱼
  17. 某选秀比赛的晋级规则是:如果7个评委中,有4个及以上评委投赞成票。试用数组编写程序判断某选手是否晋级
  18. 阿里云服务器安装MySQL及配置
  19. java及大数据程微信交流群
  20. 多表联合查询、嵌套查询

热门文章

  1. windows环境搭建web服务器(IIS)
  2. 发邮件的主题是什么意思?
  3. PHPMailer不能连接SMTP服务器的两种常见原因(Could not connect to SMTP host)
  4. SPSS的如何进行组内相关系数分析
  5. SpringMybatis
  6. Linux SIGABRT和abort()函数
  7. jquery1.4.4 ajax在页面关闭时无法abort,jquery ajax abort()的使用方法
  8. 北京市海淀区2012届高三上学期期末考试生物试题
  9. 使用coplot()绘制多元图
  10. 平分肥宅快乐水之辗转相除法