@angular/router 原始碼分析之註冊路由

lx1036發表於2018-07-10

@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 的格式可見下圖:

url format

對於上圖中的 URL,@angular/router 會呼叫 Router.parseUrl(), 實際上還是呼叫 UrlSerializer.parse() 來把 URL 字串 '/section-one;test=one/(nav:navigation;test=two//main:about;test=three)?query=four#frag' 解析為 UrlTree 物件:

url-tree

@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

router_state

第四個點是呼叫 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 的呢?見本系列第二篇文章。

相關文章