路由
(route)
,几乎所有的MVC(VM)
框架都应该具有的特性,因为它是前端构建单页面应用(SPA)
必不可少的组成部分。
那么,对于angular
而言,它自然也有内置
的路由模块:叫做ngRoute
。
不过,大家很少用它,因为它的功能太有限,往往不能满足开发需求!!
于是,一个基于ngRoute
开发的第三方路由模块,叫做ui.router
,受到了大家的“追捧”。
首先,无论是使用哪种路由,作为框架额外的附加功能,它们都将以模块依赖
的形式被引入,简而言之就是:在引入路由源文件
之后,你的代码应该这样写(以ui.router
为例):
angular.module("myApp", ["ui.router"]); // myApp为自定义模块,依赖第三方路由模块ui.router
这样做的目的是:在程序启动(bootstrap)的时候,加载依赖模块(如:ui.router),将所有挂载
在该模块的服务(PRovider)
,指令(directive)
,过滤器(filter)
等都进行注册,那么在后面的程序中便可以调用了。
说到这里,就得看看ngRoute模块
和ui.router模块
各自都提供了哪些服务,哪些指令?
ngRoute
ui.router
(注
:服务提供者:用来提供服务实例和配置服务。)
这样一看,其实ui.router
和ngRoute
大体的设计思路,对应的模块划分都是一致的(毕竟是同一个团队开发),不同的地方在于功能点的实现和增强
。
那么问题来了:ngRoute
弱在哪些方面,ui.router
怎么弥补了这些方面?
这里,列举两个最重要的方面来说(其他细节,后面再说):
多视图:页面可以显示多个动态变化的不同区块。
这样的业务场景是有的:
比如:页面一个区块用来显示页面状态,另一个区块用来显示页面主内容,当路由切换时,页面状态跟着变化,对应的页面主内容也跟着变化。
首先,我们尝试着用ngRoute
来做:
html
<div ng-view>区块1</div><div ng-view>区块2</div>
js
$routeProvider .when('/', { template: 'hello world'});
我们在html中利用ng-view指令定义了两个区块,于是两个div中显示了相同的内容,这很合乎情理,但却不是我们想要的,但是又不能为力,因为,在ngRoute中:
ok,针对上述两个问题,我们尝试用ui.router
来做:
html
<div ui-view></div> <div ui-view="status"></div>
js
$stateProvider .state('home', { url: '/', views: { '': { template: 'hello world' }, 'status': { template: 'home page' } } });
这次,结果是我们想要的,两个区块,分别显示了不同的内容,原因在于,在ui.router中:
注
:视图名是一个字符串,不可以包含@
(原因后面会说)。
嵌套视图:页面某个动态变化区块中,嵌套着另一个可以动态变化的区块。
这样的业务场景也是有的:
比如:页面一个主区块显示主内容,主内容中的部分内容要求根据路由变化而变化,这时就需要另一个动态变化的区块嵌套在主区块中。
其实,嵌套视图,在html中的最终表现就像这样:
<div ng-view>I am parent<div ng-view>I am child</div></div>
$routeProvider .when('/', { template: 'I am parent <div ng-view>I am child</div>'});
倘若,你真的用ngRoute
这样写,你会发现浏览器崩溃了,因为在ng-view指令link的过程中,代码会无限递归下去。
那么造成这种现象的最根本原因:路由没有明确的父子层级关系!
看看ui.router
是如何解决这一问题的?
$stateProvider .state('parent', { abstract: true, url: '/', template: 'I am parent <div ui-view></div>' }) .state('parent.child', { url: '', template: 'I am child' });
parent
与parent.child
来确定路由的父子关系
,从而解决无限递归问题。路由,大致可以理解为:一个
查找匹配
的过程。
对于前端MVC(VM)
而言,就是将hash值
(#xxx)与一系列的路由规则
进行查找匹配,匹配出一个符合条件的规则,然后根据这个规则,进行数据的获取,以及页面的渲染。
所以,接下来:
首先,看一个简单的例子:
$stateProvider .state('home', { url: '/abc', template: 'hello world' });
上面,我们通过调用$stateProvider.state(...)
方法,创建了一个简单路由规则,通过参数,可以容易理解到:
意思就是说:当我们访问http://xxxx#/abc
的时候,这个路由规则被匹配到,对应的模板会被填到某个div[ui-view]
中。
看上去似乎很简单,那是因为我们还没有深究具体的一些路由配置参数(我们后面再说)。
这里需要深入的是:$stateProvider.state(...)
方法,它做了些什么工作?
$urlRouterProvider.when(...)
方法,进行路由的注册
(之前是路由的创建),代码里是这样写的:$urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match, $stateParams) { // 判断是否是同一个state || 当前匹配参数是否相同 if ($state.$current.navigable != state || !equalForKeys($match, $stateParams)) { $state.transitionTo(state, $match, { inherit: true, location: false }); }}]);
上述代码的意思是:当hash值
与state.url
相匹配时,就执行后面那段回调,回调函数里面进行了两个条件判断之后,决定是否需要跳转到该state?
这里就插入了一个话题:为什么说 “跳转到该state,而不是该url”?
其实这个问题跟大家一直说的:“ui.router是基于state(状态)的,而不是url
”是同一个问题。
我的理解是这样的:之前就说过,路由存在着明确的父子关系
,每一个路由可以理解为一个state,
$state.transitionTo(state, ...);
,这样的话,它的父state会被激活(如果还没有激活的话),它的子state会被销毁(如果已经激活的话)。ok,回到之前的路由注册,调用了$urlRouterProvider.when(...)
方法,它做了什么呢?
它创建了一个rule,并存储在rules集合里面,之后的,每次hash值变化,路由重新查找匹配都是通过遍历这个rules
集合进行的。
有了之前,路由的创建和注册,接下来,自然会想到路由是如何查找匹配的?
恐怕,这得从页面加载完毕说起:
$rootScope
会触发$locationChangeSuccess
事件(angular在每次浏览器hash change的时候也会触发$locationChangeSuccess
事件)$locationChangeSuccess
事件,于是开始通过遍历一系列rules,进行路由查找匹配$state.transitionTo(state,...)
,跳转激活对应的state可以从下面这段源代码看到,看到查找匹配的起始和过程:
function update(evt) { // ...省略 function check(rule) { var handled = rule($injector, $location);// handled可以是返回:// 1. 新的的url,用于重定向// 2. false,不匹配// 3. true,匹配 if (!handled) return false; if (isString(handled)) $location.replace().url(handled); return true; } var n = rules.length, i; // 渲染遍历rules,匹配到路由,就停止循环 for (i = 0; i < n; i++) { if (check(rules[i])) return; } // 如果都匹配不到路由,使用otherwise路由(如果设置了的话) if (otherwise) check(otherwise);}function listen() { // 监听$locationChangeSuccess,开始路由的查找匹配 listener = listener || $rootScope.$on('$locationChangeSuccess', update); return listener;}if (!interceptDeferred) listen();
那么,问题来了:难道每次路由变化(hash变化),由于监听了’$locationChangeSuccess'事件,都要进行rules的遍历
来查找匹配路由,然后跳转到对应的state吗?
答案是:肯定的,一般的路由器都是这么做的,包括ngRoute。
那么ui.router对于这样的问题,会怎么进行优化
呢?
回归到问题:我们之所以要循环遍历rules,是因为要查找匹配到对应的路由(state),然后跳转过去,倘若不循环,能直接找到对应的state吗?
答案是:可以的。
还记得前面说过,在用ui.router在创建路由时:
根据以上两点,于是ui.router提供了另一个指令叫做:ui-sref指令
,来解决这个问题,比如这样:
<a ui-sref="home">通过ui-sref跳转到home state</a>
当点击这个a标签时,会直接跳转到home state,而并不需要循环遍历rules,ui.router是这样做到的(这里简单说一下):
新闻热点
疑难解答