筆者的學習進度比較慢,直到兩年以前寫的網站都還是以服務端為主導的,即網站的所有視圖都由服務器視圖模板來渲染,筆者使用的是 DotNet MVC,開發套路就是在Controller里面寫Action,在Views里寫對應的Action.cshtml,使用ajax發起請求已經是比較前端的事情了。這種時候由於DotNet MVC框架繼承的微軟風格的懶人模式,甚至不需要去知道其路由是如何實現的,給人一種感覺是只要在瀏覽器里敲進去Controller名與Action名,就訪問到視圖了。
后來筆者開始使用ng1前端框架,起初的開發完全放棄其路由功能,而使用服務端MVC路由來區分頁面,在各自頁面內寫ng1代碼。現在想來這樣做實在是浪費了ng1的能力,因為.Net MVC的視圖能力雖說有布局頁這些概念,但其仍然是多頁面的應用,視圖間的切換都會造成頁面的刷新,這會導致每切換一個頁面都會重新加載一次ng1近兩萬行的代碼。根本原因是沒有利用這個強大的前端框架的精髓之一——前端路由。
拋開前端路由的底層,其所做的事情就是動態操作DOM來模擬頁面的切換,帶來的好處是巨大的,首先是不用再頁面切換時重復加載大量的腳本依賴了,還有就是頁面切換不再是白屏讀條,而是可以加上接近原生應用的切換效果。而使用了前端路由后要解決的最大問題,自然是如何與服務端路由分離,至少不造成沖突。
前端路由有兩種形式,一種是Html5的pushState風格,一種是使用#符號實現與服務端路由的分隔,筆者在基於微信公眾號開發的時候涉及到了使用前端路由的SPA與微信API交互的各種情況,發現這兩種形式各有問題,只能說微信以及傳統的web服務器(至少IIS這個毒瘤)對現在這些前后端分離的SPA應用還不怎么友好。
一、ng2項目的部署
由於ng2采用了各種web新特性,包括TypeScript語法特性以及基於SystemJS或者webpack的代碼模塊化,已經是個徹底的客戶端web體系,可以說若想要使用ng2來開發項目的話,就擺脫不了前后端分離的架構了。而說到web的前后端分離,其前端(客戶端)雖說跟服務端(WebApi)分離了,但卻也仍然需要一個服務器來支撐前端入口頁面以及框架腳本的文件分發。這就涉及到如何部署ng2的網站了,筆者得到的總結是只要ng2使用webpack打包編譯得到目標文件后,放到任何服務器上都可以——因為得到的全都是靜態資源,需要的只是架設成一個靜態網站就足夠了。一種選擇是直接使用node圈子提供的服務器,並使用nginx完成反向代理等工作,由於某些歷史原因筆者將ng2網站部署到了IIS服務器上,也是可以運行的。
但是在IIS上部署ng2項目會有一個最直接的問題,那就是前后端路由沖突了(使用html5 pushState風格的情況下)!解決的辦法有兩個:
1. 老老實實使用#風格來區分前后端路由。其實前后端分離后沒有了后端路由,此時的網站地址永遠是 domain/#/url 格式的。
2. 筆者在使用#方式時發現此方式下發起微信網頁授權的話會發生很尷尬的問題。
配置給微信那邊的回調地址應該是這樣的格式:
domain/#/url?code=xxxxx&state=xxxxx
但是至少目前的微信授權接口,會自作聰明處理#符號,導致其會回調到這樣的路徑:
domain/?code=xxxxx&state=xxxxx/#/url
也就是說微信這廝居然認為#后邊的參數不再參與路由了,強行把回調參數提到#號之前,簡直蛋疼。
所以使用pushState是不可避免了。(但其實在pushState風格下,微信的支付url配置又會有點問題,說多了都是淚)
網上大致有兩種辦法來防止被服務端路由影響:
1) 重寫url(沒去試過)
2) 筆者最后采用的辦法是修改404的重定向到網站入口文件
雖然感覺有一些暴力,但是仔細一想這也只是服務端的404頁面,本身也沒有任何意義了,真正的404頁面也應該是在前端路由中導航才是,所以把所有的服務器端的404請求重定向了也沒什么不妥的。
二、簡單介紹ng2下的路由配置
原本寫本文時只打算寫點ng2路由是如何代碼實現的,但是其實ng2的路由配置也就那么一點內容,感覺更麻煩的反而是整個的前端路由這個概念,所以到后半段才來講講ng2的路由配置。
路由的目標
首先要明確路由的目標,就是組件。
ng2的頁面都是基於組件的,而路由要做的就是切換各個組件,其需要依賴的就是<router-outlet>標簽,並在ng2模塊的定義中聲明即可。
比如一般我們的根模塊做的事情除了加載核心依賴之外就是要做好根路由的配置,決定頁面首先要顯示哪個頁面,以及哪幾個url對應哪幾個組件,當url匹配時就加載這些組件。而根組件中也是,除了其他全局標簽之外,必不可少的就是一個<router-outlet>標簽,所有的路由都是由這個路由插座展開的。
路由的跳轉與參數
路由的跳轉有兩種方式,一是在界面中使用路由指令:
<a [routerLink]="['/url',參數]">Click Me</a>
二就是在腳本中手動跳轉:
router.navigate(['/url', 參數])
而在新頁面中獲取路由參數也很簡單,只要使用angular2的系統路由模塊提供的ActivatedRoute服務即可。
懶加載路由
ng2的路由是支持懶加載的,確切的說懶加載的對象是ng2模塊。
因為ng2應用規模變大以后會有多個模塊,而這些模塊不可能同時被使用,肯定是當路由指向到模塊包含的某個組件(頁面)后才真正需要加載這個模塊,這時就要用到路由的懶加載能力了。其實現方式很簡單:
{ path: 'url', loadChildren: '目標模塊的文件路徑#目標模塊名' }
這樣在url匹配時,才會先找到此模塊文件的位置,然后加載此模塊,好處就是不需要再應用一開始就加載所有模塊,能節省資源,ng2甚至有模塊預加載的能力,即后台異步提前加載好指定的懶加載模塊,這樣在需要此模塊時,模塊其實已經加載完畢,速度更加提升了。
至此筆者使用的路由配置一般是這樣的形式:
1. 在應用的根模塊中配置根路由,根路由只配置子頁面所屬模塊的懶加載配置,以及必要的入口頁面對應組件的直接加載。
1 const routes: Routes = [ 2 { path: 'directive', loadChildren: './directive/directive.module#DirectiveModule' }, 3 { path: '', redirectTo: '/', pathMatch: 'full'}, 4 { path: '**', redirectTo: '/', pathMatch: 'full' } 5 ]; 6 7 @NgModule({ 8 imports: [RouterModule.forRoot(routes)], 9 exports: [RouterModule], 10 declarations: [ ], 11 providers: [ ] 12 }) 13 export class AppRoutingModule { }
2.具體的模塊中配置子路由,在子路由中詳細指定自路徑對應的子組件
const routes: Routes = [ { path: '', component: DirectiveComponent, children: [ { path: 'home', component: DirectiveHomeComponent } ] }, { path: '**', component: DirectiveHomeComponent } ]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule], providers: [] })
具體的路徑匹配規則與路由模塊的聲明方式等這里就不提了,沒有什么難度可言,關鍵在於其路由方式的理解。
路由動畫的問題
筆者遇到的目前ng2的路由體系的一個很大的問題就是路由動畫。
由於ng2的路由依賴於一個<router-outlet>標簽,而實際的組件(頁面)的切換過程都是:
初始化頁面1 -> 定向到頁面2 -> 銷毀頁面1 -> 初始化頁面2
ng2的路由組件切換非常盡職的會先銷毀前一個組件再創建后一個組件,這導致即使你給組件配置了很酷炫的切換動畫,前一個被銷毀的組件也會直接蒸發,而后一個組件會正常按動畫來出現。看到的效果就是,每次路由切換,頁面都會先變成一片空白,然后新頁面姍姍來遲,滑動進來也好,淡出顯示也好,都給人一種尷尬的感覺。StackOverflow上有大牛給出了很厲害的解決方案,但給筆者的感覺是,僅為了路由的切換這也太不優雅了,期待ng2官方在未來的更新中給出一套更優雅的路由切換動畫方案。
總結:
如本文的篇幅一樣,ng2的路由其實沒多少東西,但是卻問題重重,通常要解決的不是路由路配置,而是如何讓這個還比較新的前端路由體系適應於當前已有的互聯網服務的大環境。