Angular路由: 理解路由状态

翻译/大漠穷秋

摘要: Angular路由: 理解路由状态

原文点这里Victor Savkin 是Angular核心团队成员,路由模块就是他开发的。

 

一个Angular2应用就是一颗由组件构成的tree。其中有些组件是可复用的UI组件(例如:list、table);有些是应用组件,这些组件代表了应用里面的某些界面或者逻辑片段。路由关注的是应用组件,更确切地说,关注的是它们的布局。我们把这种“组件布局”称为路由状态(router state)。换句话说,路由状态(router state)就是应用组件的布局方式,这些组件定义了当前屏幕上能看到的内容。

本文将会深度解析RouterState。

本文是以Angular 2 Router 这本书为基础的,你可以在以下链接找到这本书https://leanpub.com/router 。这本书的内容远胜于入门手册,它深入解析了关于路由的方方面面。心智模型、设计约束,以及API里面的各种细节,可以说无所不包。如果你觉得这篇文章不错,强烈推荐去看看这本书!

 

RouterState与RouterStateSnapshot

当我们进行导航的时候,在跳转完成之后,路由会创建一个RouterStateSnapshot(含义为:路由状态快照---译者注)实例。那么什么是RouterStateSnapshot呢?它与RouterState有什么区别呢?

RouteStateSnapshot是一个不可变数据结构,代表了路由在某一个特定时刻的状态。任何时候,只要增加、删除了组件,或者参数发生了改变,就会创建一个新的快照。

RouterState与RouteStateSnapshot比较类似,但是RouterState所代表的路由状态会随着时间而改变。

 

RouterStateSnapshot

如你所见,RouterStateSnapshot是tree型数据结构,它由当前活动的路由快照所构成。树里面的每一个节点都知道“被消费的”URL段、提取的参数,以及解析出的数据。为了更清晰地理解这些内容,我们来看以下例子:

当我们导航到 “/inbox/33/messages/44”这个路径的时候,路由会查看URL,然后构造出下面这样的RouterStateSnapshot实例:

然后,路由会实例化ConversationCmp,里面会包含MessageCmp。

现在,假设我们导航到另一个URL “/inbox/33/messages/45”,路由会生成以下快照:

为了避免不必要的DOM操作,当路径发生的变化的时候,路由会复用相应的组件。在当前这个例子里面,消息组件(MessageCmp)的id参数从44变成了45。这就意味着,我们不能简单地把ActivatedRouteSnapshot 注入给MessageCmp就完了,因为快照里面的id参数会一直保持为44,而它已经过时了。

路由状态快照代表了应用在某一个特定时刻的状态,正如名字“快照”(snapshot)所表达的含义那样。但是,组件可能会存活数小时,它们所显示的数据也会发生改变。所以,只有快照是无法满足需求的---我们需要一种能处理变化的数据结构。

 

引入RouterState!

RouterState和ActivatedRoute与它们的快照版本类似,不同点在于:它们暴露出来的所有值都是observable(可观察的,简单说就是可以派发各种事件的JS对象---译者注),这种类型非常适合用来处理随着时间变化的值。

对于所有被路由实例化的组件,都可以把ActivatedRoute实例注射给它。

如果我们从 “/inbox/33/messages/44”导航到“/inbox/33/messages/45”,data这个observable会通过新的message对象派发出一组新的数据,然后组件就会显示Message 45相关的内容了。

 

访问快照

路由把parameter属性和data属性都暴露成了observable,在绝大多数情况下这样会非常方便,但并非所有情况都如此。有时候,我们就是想要一个状态快照,这样我们才能立即检查一下其中的数据。

 

ActivatedRoute

ActivatedRoute提供了访问url、parameter、data、queryParams,以及fragment这些observable属性的途径。接下来我们会逐个详细解释,但是在此之前我们需要先来审查一下这些属性之间的关系。

URL的变化是导致路由变化的原动力。由于用户可以直接手动修改路径,所以它必须表现成现在这个样子。

每次URL发生变化的时候,路由就会从里面提取出参数集:路由会从URL里面取出占位参数(例如id),以及从匹配到的最后一个URL段里面获取到的矩阵参数,然后把它们结合在一起。换句话说,相同的URL总是会导出同样的参数集。

接下来,路由会调用对应的数据解析器(data resolver)并把结果整合到一开始提供的静态数据里面去。由于数据解析器可以是任意函数,所以路由不会保证对于相同的URL都会返回给你同一个对象。情况可能会更加复杂!由于URL里面会包含资源的id参数,而这个参数是固定的,数据解析器会根据这个id去获取对应的资源内容,而内容本身是会随着时间变化的。

最后,当前活动的路由对象还会提供queryParams和fragment这两个observable属性。与其它局限在特定路由里面的observable属性相反,queryParams和fragment可以在多个路由里面共享。

 

URL

假设有以下代码:

我们首先导航到 “/inbox/33/messages/44”,然后再导航到“/inbox/33/messages/45”,会看到以下结果:

url [{path: ‘messages’, params: {}}, {path: ‘44’, params: {}}]

url [{path: ‘messages’, params: {}}, {path: ‘45’, params: {}}]

我们通常并不会监听URL本身的变化,因为那样太底层了。直接监听URL变化的一个实战案例是组件由通配符激活的情况。由于在这种情况下,URL段所构成的数组不是固定的,所以我们可能需要检查这个数组来给用户显示不同的数据。

 

Params

假设有以下代码:

当我们先导航到“/inbox/33/messages;a=1/44;b=1”,然后再导航到“/inbox/33/messages;a=2/45;b=2”的时候,我们会看到:

params {id: ‘44’, b: ‘1’}

params {id: ‘45’, b: ‘2’}

需要注意的第一件事情是:id参数的类型是字符串(在处理URL的时候,我们总是使用字符串类型)。其次需要注意:路由只会提取URL里面最后一个小段中的矩阵参数(就是URL最后一个反斜线后面的内容---译者注)。这就是为什么结果里面不存在参数’a’的原因。

 

Data

我们稍稍调整一下以上例子里面的配置项,看看data这个observable属性是如何运行的。

其中MessageResolver的定义如下:

data属性可以用来给当前活动的路由传递一个固定的对象。这个对象在整个应用的生命周期里面都不会改变。而resolve这个属性是用来传递动态数据的。

注意以上配置项里面的“message: MessageResolver” 这一行,它不是用来命令路由去实例化解析器(resolver)的。它命令路由通过依赖注入机制去获取一个解析器。这就意味着你必须在某个地方的providers列表里面注册一个“MessageResolver”。

一旦路由获取到了resolver实例,它就会调用其中的’resolve’方法。这个方法可以返回一个promise、一个observable,或者一个其它对象。如果返回值是一个promise或者一个observable,在进行其它操作之前路由会一直等待,直到这个promise或者observable完成为止。

resolver没有必要一定是一个实现了’Resolve’接口的类。它也可以是一个函数:

路由会把解析好的数据和静态数据整合到一个属性里面去,这个属性你是可以访问的,示例如下:

当我们先导航到“/inbox/33/message/44” ,然后再导航到“/inbox/33/messages/45”的时候,将会看到以下结果:

data {allowReplyAll: true, message: {id: 44, title: ‘Rx Rocks’, …}}

data {allowReplyAll: true, message: {id: 45, title: ‘Angular Rocks’, …}}

 

queryParameters和fragment属性

与局限在特定路由中的其它observable相反,queryParameters和fragment属性可以在多个路由之间共享。

 

本文来源:https://my.oschina.net/mumu/blog/780317


如果给你带来帮助,欢迎微信或支付宝扫一扫,赞一下。