书签 分享 收藏 举报 版权申诉 / 19

类型原创uirouter源码解析.docx

  • 文档编号:8174649
  • 上传时间:2023-01-29
  • 格式:DOCX
  • 页数:19
  • 大小:57.13KB

区块2

js代码:

$routeProvider

.when('/',{

template:

'helloworld'

});

我们在html中利用ng-view指令定义了两个区块,于是两个div中显示了相同的内容,这很合乎情理,但却不是我们想要的,但是又不能为力,因为,在ngRoute中:

1、视图没有名字进行唯一标志,所以它们被同等的处理。

2、路由配置只有一个模板,无法配置多个。

ok,针对上述两个问题,我们尝试用ui.router来做:

html代码:

js代码:

$stateProvider

.state('home',{

url:

'/',

views:

{

'':

{

template:

'helloworld'

},

'status':

{

template:

'homepage'

}

}

});

这次,结果是我们想要的,两个区块,分别显示了不同的内容,原因在于,在ui.router中:

可以给视图命名,如:

ui-view=”status”。

可以在路由配置中根据视图名字(如:

status),配置不同的模板(其实还有controller等)。

注:

视图名是一个字符串,不可以包含@(原因后面会说)。

嵌套视图

嵌套视图:

页面某个区块能动态变化。

这样的业务场景也是有的:

比如:

页面一个主区块显示主内容,主内容中的部分内容要求根据路由变化而变化,这时就需要另一个动态变化的区块嵌套在主区块中。

其实,嵌套视图,在html中的最终表现就像这样:

Iamparent

Iamchild

转成javascript,我们会在程序里这样写:

$routeProvider

.when('/',{

template:

'IamparentIamchild

'

});

倘若,你真的用ngRoute这样写,你会发现浏览器崩溃了,因为在ng-view指令link的过程中,代码会无限递归下去。

那么造成这种现象的最根本原因:

路由没有明确的父子层级关系!

看看ui.router是如何解决这一问题的?

$stateProvider

.state('parent',{

abstract:

true,

url:

'/',

template:

'Iamparent'

})

.state('parent.child',{

url:

'',

template:

'Iamchild'

});

巧妙地,通过parent与parent.child来确定路由的父子关系,从而解决无限递归问题。

另外子路由的模板最终也将被插入到父路由模板的div[ui-view]中去,从而达到视图嵌套的效果。

ui.router工作原理

路由,大致可以理解为:

一个查找匹配的过程。

对于前端MVC(VM)而言,就是将hash值(#xxx)与一系列的路由规则进行查找匹配,匹配出一个符合条件的规则,然后根据这个规则,进行数据的获取,以及页面的渲染。

所以,接下来:

第一步,学会如何创建路由规则?

第二步,了解路由查找匹配原理?

路由的创建

首先,看一个简单的例子:

$stateProvider

.state('home',{

url:

'/abc',

template:

'helloworld'

});

上面,我们通过调用$stateProvider.state(...)方法,创建了一个简单路由规则,通过参数,可以容易理解到:

规则名:

’home’

匹配的url:

’/abc’

对应的模板:

’helloworld’

意思就是说:

当我们访问http:

//xxxx#/abc的时候,这个路由规则被匹配到,对应的模板会被填到某个div[ui-view]中。

看上去似乎很简单,那是因为我们还没有深究具体的一些路由配置参数(我们后面再说)。

这里需要深入的是:

$stateProvider.state(...)方法,它做了些什么工作?

首先,创建并存储一个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被激活,同时,它对应的父路由state也将被激活。

我们还可以手动的激活某一个state,就像上面写的那样,$state.transitionTo(state,...);,这样的话,它的父state会被激活(如果还没有激活的话),它的子state会被销毁(如果已经激活的话)。

ok,回到之前的路由注册,调用了$urlRouterProvider.when(...)方法,它做了什么呢?

它创建了一个rule,并存储在rules集合里面,之后的,每次hash值变化,路由重新查找匹配都是通过遍历这个rules集合进行的。

路由的查找匹配

有了之前,路由的创建和注册,接下来,自然会想到路由是如何查找匹配的?

恐怕,这得从页面加载完毕说起:

angular在刚开始的$digest时,$rootScope会触发$locationChangeSuccess事件(angular在每次浏览器hashchange的时候也会触发$locationChangeSuccess事件)

ui.router监听了$locationChangeSuccess事件,于是开始通过遍历一系列rules,进行路由查找匹配

当匹配到路由后,就通过$state.transitionTo(state,...),跳转激活对应的state

最后,完成数据请求和模板的渲染

可以从下面这段源代码看到,看到查找匹配的起始和过程:

functionupdate(evt){

//...省略

functioncheck(rule){

varhandled=rule($injector,$location);

//handled可以是返回:

//1.新的的url,用于重定向

//2.false,不匹配

//3.true,匹配

if(!

handled)returnfalse;

if(isString(handled))$location.replace().url(handled);

returntrue;

}

varn=rules.length,i;

//渲染遍历rules,匹配到路由,就停止循环

for(i=0;i

if(check(rules[i]))return;

}

//如果都匹配不到路由,使用otherwise路由(如果设置了的话)

if(otherwise)check(otherwise);

}

functionlisten(){

//监听$locationChangeSuccess,开始路由的查找匹配

listener=listener||$rootScope.$on('$locationChangeSuccess',update);

returnlistener;

}

if(!

interceptDeferred)listen();

那么,问题来了:

难道每次路由变化(hash变化),由于监听了’$locationChangeSuccess’事件,都要进行rules的遍历来查找匹配路由,然后跳转到对应的state吗?

答案是:

肯定的,一般的路由器都是这么做的,包括ngRoute。

那么ui.router对于这样的问题,会怎么进行优化呢?

回归到问题:

我们之所以要循环遍历rules,是因为要查找匹配到对应的路由(state),然后跳转过去,倘若不循环,能直接找到对应的state吗?

答案是:

可以的。

还记得前面说过,在用ui.router在创建路由时:

会实例化一个对应的state对象,并存储起来(states集合里面)

每一个state对象都有一个state.name进行唯一标识(如:

’home’)

根据以上两点,于是ui.router提供了另一个指令叫做:

ui-sref指令,来解决这个问题,比如这样:

通过ui-sref跳转到homestate

当点击这个a标签时,会直接跳转到homestate,而并不需要循环遍历rules,ui.router是这样做到的(这里简单说一下):

首先,ui-sref=”home”指令会给对应的dom添加click事件,然后根据state.name,直接跳转到对应的state,代码像这样:

element.bind("click",function(e){

//..省略若干代码

vartransition=$timeout(function(){

//手动跳转到指定的state

$state.go(ref.state,params,options);

});

});

跳转到对应的state之后,ui.router会做一个善后处理,就是改变hash,所以理所当然,会触发’$locationChangeSuccess’事件,然后执行回调,但是在回调中可以通过一个判断代码规避循环rules,像这样:

functionupdate(evt){

varignoreUpdate=lastPushedUrl&&$location.url()===lastPushedUrl;

//手动调用$state.go(...)时,直接return避免下面的循环

if(ignoreUpdate)returntrue;

//省略下面的循环ruls代码

}

说了那么多,其实就是想说,我们不建议直接使用href="#/xxx"来改变hash,然后跳转到对应state(虽然也是可以的),因为这样做会多了一步rules循环遍历,浪费性能,就像下面这样:

通过href跳转到homestate

路由详解

这里详细地介绍ui.router的参数配置和一些深层次用法。

不过,在这之前,需要一个demo,ui.router的官网demo无非就是最好的学习例子,里面涉及了大部分的知识点,所以接下来的代码讲解大部分都会是这里面的(建议下载到本地进行代码学习)。

为了更好的学习这个demo,我画了一张图来描述这个demo的contacts部分各个视图模块,如下:

父与子

之前就说到,在ui.router中,路由就有父与子的关系(多个父与子凑起来就有了,祖先和子孙的关系),从javascript的角度来说,其实就是路由对应的state对象之间存在着某种引用的关系。

用一张数据结构的表示下contacts部分,大概是这样(原图):

上面的图看着有点乱,不过没关系,起码能看出各个state对象之间通过parent字段维系了这样一个父与子的关系(粉红色的线)。

ok,接下来就看下是如何定义路由的父子关系的?

假设有一个父路由,如下:

$stateProvider

.state('contacts',{});

ui.router提供了几种方法来定义它的子路由:

1.点标记法(推荐)

$stateProvider

.state('contacts.list',{});

通过状态名简单明了地来确定父子路由关系,如:

状态名为’a.b.c’的路由,对应的父路由就是状态名为’a.b’路由。

2.parent属性

$stateProvider

.state({

name:

'list',//状态名也可以直接在配置里指定

parent:

'contacts'//父路由的状态名

});

或者:

$stateProvider

.state({

name:

'list',//状态名也可以直接在配置里指定

parent:

{//parent也可以是一个父路由配置对象(指定路由的状态名即可)

name:

'contacts'

}

});

通过parent直接指定父路由,可以是父路由的状态名(字符串),也可以是一个包含状态名的父路由配置(对象)。

竟然路由有了父与子的关系,那么它们的注册顺序有要求嘛?

答案是:

没有要求,我们可以在父路由存在之前,创建子路由(不过,不是很推荐),因为ui.router在遇到这种情况时,在内部会帮我们先缓存子路由的信息,等待它的父路由注册完毕后,再进行子路由的注册。

模板渲染

当路由成功跳转到指定的state时,ui.router会触发'$stateChangeSuccess'事件通知所有的ui-view进行模板重新渲染。

代码是这样的:

if(options.notify){

$rootScope.$broadcast('$stateChangeSuccess',to.self,toParams,from.self,fromParams);

}

而ui-view指令在进行link的时候,在其内部就已经监听了这一事件(消息),来随时更新视图:

scope.$on('$stateChangeSuccess',function(){

updateView(false);

});

大体的模板渲染过程就是这样的,这里遇到一个问题,就是:

每一个div[ui-view]在重新渲染的时候如何获取到对应视图模板的呢?

要想知道这个答案,

首先,我们得先看一下模板如何设置?

一般在设置单视图的时候,我们会这样做:

$stateProvider

.state('contacts',{

abstract:

true,

url:

'/contacts',

templateUrl:

'app/contacts/contacts.html'

});

在配置对象里面,我们用templateUrl指定模板路径即可。

如果我们需要设置多视图,就需要用到views字段,像这样:

$stateProvider

.state('contacts.detail',{

url:

'/{contactId:

[0-9]{1,4}}',

views:

{

'':

{

templateUrl:

'app/contacts/contacts.detail.html',

},

'hint@':

{

template:

'Thisiscontacts.detailpopulatingthe"hint"ui-view'

},

'menuTip':

{

templateProvider:

['$stateParams',function($stateParams){

return'


ContactID:

'+$stateParams.contactId+'';

}]

}

}

});

这里我们使用了另外两种方式设置模板:

1、template:

直接指定模板内容,另外也可以是函数返回模板内容;

2、templateProvider:

通过依赖注入的调用函数的方式返回模板内容;

上述我们介绍了设置单视图和多视图模板的方式,其实最终它们在ui.router内部都会被统一格式化成的views的形式,且它们的key值会做特殊变化:

上述的单视图会变成这样:

views:

{

//模板内容会被安插在根路由模板(index.html)的匿名视图下

'@':

{

abstract:

true,

url:

'/contacts',

templateUrl:

'app/contacts/contacts.html'

}

}

多视图会变成这样:

views:

{

//模板内容会被安插在父路由(contacts)模板的匿名视图下

'@contacts':

{

templateUrl:

'app/contacts/contacts.detail.html',

},

//模板内容会被安插在根路由(index.html)模板的名为hint视图下

'hint@':

{

template:

'Thisiscontacts.detailpopulatingthe"hint"ui-view'

},

//模板内容会被安插在父路由(contacts)模板的名为menuTip视图下

'menuTip@contacts':

{

templateProvider:

['$stateParams',function($stateParams){

return'


ContactID:

'+$stateParams.contactId+'';

}]

}

}

我们会发现views对象里面的key变化了,最明显的是出现了一个@符号,其实这样的key值是ui.router的一个设计,它的原型是:

viewName+'@'+stateName,解释下:

1、viewName:

指的是ui-view="status"中的’status’,也可以是“”(空字符串),因为会有匿名的ui-view或者ui-view=""。

2、stateName:

默认情况下是父路由的state.name,因为子路由模板一般都安插在父路由的ui-view中。

也可以是“”(空字符串),表示最顶层rootState;还可以是任意的祖先state.name。

这样原型的意思是,表示该模板(stateName路由对应的模板)将会被安插在viewName视图下(可以看看上面代码中的注释理解下)。

其实这也解释了之前我说的:

“为什么state.name里面不能存在@符号”?

因为@在这里被用于特殊含义了。

所以,到这里,我们就知道在ui-view重新进行模板渲染时,是根据viewName+'@'+stateName来获取对应的视图模板内容(其实还有controller等)的。

其实,由于路由有了父与子的关系,某种程度上就有了override(覆盖或者重写)可能。

父路由和子路由之间就存在着视图的override,像下面这段代码:

$stateProvider

.state('contacts.detail',{

url:

'/{contactId:

[0-9]{1,4}}',

views:

{

'hint@':

{

template:

'Th

配套讲稿:

如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。

特殊限制:

部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。

关 键  词:
原创 uirouter 源码 解析
提示  冰豆网所有资源均是用户自行上传分享,仅供网友学习交流,未经上传用户书面授权,请勿作他用。
关于本文
本文标题:原创uirouter源码解析.docx
链接地址:https://www.bdocx.com/doc/8174649.html

copyright@ 2008-2022 冰点文档网站版权所有

经营许可证编号:鄂ICP备2022015515号-1