@angular/router 源码分析之注册路由

@angular/router 模块主要解决程序路由状态改变和懒加载模块问题。

比如,程序从路由状态 state1: /advisors/1/households/1 转变为路由状态 state2: /advisors/1/accounts/2,需要实例化的组件集合也从 components1: Advisor+Household 转变为 components2: Advisor+Account(准确的说应该是先是 Module 的实例化,然后才是组件的实例化),这个过程是如何实现的?

另外,对于按需加载的模块,又该如何加载该模块,并且将该模块显示在对应位置处?

@angular/router 模块就是用来解决路由状态改变和懒加载模块问题的。本文主要解释程序启动后,@angular/router 是如何注册开发者定义的路由集合的,和实例化 Router 对象的。

程序启动后,即调用 PlatformRef.bootstrapModule(AppModule) 后,会执行导入的 RouterModule.forRoot(routes: Routes) 来合并 RouterModule 提供的服务,且 routes 路由集合是由开发者自定义的,比如:

routes: Routes = [
    {path: 'advisors/:id', component: AdvisorComponent, children: [
        {path: 'households/:id', component: HouseholdComponent},
        {path: 'accounts/:id', component: AccountComponent},
    ]},
];

一起看看 RouterModule 能给我们提供哪些重点对象吧:RouterModule.forRoot(routes)

第一个对象是来自于 @angular/common 的 Location,用来表示浏览器的 url,并提供了 forward(),back(),go() 等重要方法用来改变 url,同时提供了 HashLocationStrategyPathLocationStrategy 两种策略生成是否带有 ‘#’ 的 url,至于为何需要两种不同风格的 url 原因可以看 中文官网描述

第二个对象是序列化 URL 的对象 UrlSerializer,@angular/router 使用 UrlTree 对象存储一个 URL,比如 ‘/advisors/1/accounts/2?type=loan#fragment’,并且 UrlTree 对象又使用 UrlSegmentGroup 对象来表示 URL 的 path 部分,这里 UrlSegmentGroup 表示的就是 ‘/advisors/1/accounts/2’ 这部分,UrlSegmentGroup 对象也可以存储 ‘/advisors/1/accounts/2/(user/john//bank:abc)?type=loan#fragment’ 这样的多重 URL,该多重 URL 可以表示为两个 URL: ‘/advisors/1/accounts/2/user/john?type=loan#fragment’‘/advisors/1/accounts/2/abc?type=loan#fragment’(该 URL 的出口 outlet 是 bank),虽然是多重 URL,但只需要用一个对象 UrlSegmentGroup 就可以存储,而 UrlSegmentGroup 又使用 Segment 对象来表示当前 group 内的每一个 ‘/’ 之间的部分。Url 的格式可见下图:

《@angular/router 源码分析之注册路由》

对于上图中的 URL,@angular/router 会调用 Router.parseUrl(), 实际上还是调用 UrlSerializer.parse() 来把 URL 字符串 ‘/section-one;test=one/(nav:navigation;test=two//main:about;test=three)?query=four#frag’ 解析为 UrlTree 对象:

《@angular/router 源码分析之注册路由》

@angular/router 使用 UrlTree 对象来存储 URL 字符串,并使用 UrlSerializer 来解析和序列化 URL。这块知识点还是很重要的。

第三个对象 Router,也是 @angular/router 模块中最重要的对象,使用 setupRouter 方法来初始化 Router,初始化逻辑主要是它的 构造函数,开发者自定义的 routes 集合 也是作为依赖来构造 Router 对象。第一个点就是首先调用 createEmptyUrlTree 方法创建一个 空的 UrlTree;第二个点就是实例化一个路由加载器 loader,当开发者定义了 route.loadChildren 属性时,该 loader 就会使用 loader.load() 方法去异步加载模块,所以该 loader 对象是用来解决懒加载问题的;第三个点是调用 createEmptyState 方法创建一个空 RouterState,RouterState 对象表示当前激活路由的状态(RouterState is a tree of activated routes.),它也是一个树形数据结构,用来存储 当前激活路由 的数据,该树的节点使用 ActivatedRoute 对象表示,比如对于上文中开发者定义的路由列表,当 URL 为 ‘/advisors/1/households/1’ 时,这时 RouterState 对象表示的状态树,如下红色显示部分的子树,而每一个包含组件的层级即是 ActivatedRoute

《@angular/router 源码分析之注册路由》

第四个点是调用 processNavigations() 执行路由状态切换,实际上 @angular/router 的作用就是控制路由状态的切换,所以 整个 @angular/router 的核心代码就是 processNavigations() 方法。该方法订阅了一个 BehaviorSubject 对象,只要该 BehaviorSubject 流对象弹射出一个新值,就会运行 executeScheduledNavigation(),不管是不是刷新 URL,都会运行 runNavigate(),所以精确的说,runNavigate() 这一百行左右代码才是 @angular/router 包最最核心的代码。这一百来行代码具体分为几个步骤:

第四个重要的对象就是模块工厂加载器 NgModuleFactoryLoader,该对象来自于 @angular/core 核心包,主要用来辅助 RouterConfigLoader 对象,懒加载模块时可以异步加载远程模块。

第五个重要的对象就是提供了预加载对象 RouterPreloader,用来预加载所有懒加载模块,从而提高性能。

第六个重要的对象就是 RouterInitializer,提供了初始导航功能。当程序首次初始化和启动时,调用 RouterInitializer.appInitializer()RouterInitializer.bootstrapListener() 来进行初始化导航,最后还是调用 Router.initialNavigation() 来首次导航到 URL 对应的 RouterState

所以,@angular/router 首次初始化时,提供的最重要对象是 Router,其他一切对象和逻辑都是围绕着 Router 对象展开。@angular/router 是如何根据当前 URL 查找到对应的 route 的呢?见本系列第二篇文章。

    原文作者:lx1036
    原文地址: https://segmentfault.com/a/1190000015862123
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞