本文主要介紹了vue-router相關基礎知識及單頁面應用的工做原理,寫的十分的全面細緻,具備必定的參考價值,對此有須要的朋友能夠參考學習下。若有不足之處,歡迎批評指正。html
單頁面工做原理是經過瀏覽器URL的#後面的hash變化就會引發頁面變化的特性來把頁面分紅不一樣的小模塊,而後經過修改hash來讓頁面展現咱們想讓看到的內容。前端
那麼爲何hash的不一樣,爲何會影響頁面的展現呢?瀏覽器在這裏面作了什麼內容。之前#後面的內容通常會作錨點,可是會定位到一個頁面的某個位置,這個是怎麼作到的呢,和咱們如今的路由有什麼不一樣。(我能想到一個路由的展現就會把其餘路由隱藏,是這樣的嗎)後面會看一看寫一下這個疑惑,如今最重要的是先把基本概念弄熟。vue
當你要把 vue-router 添加進來,咱們須要作的是,將組件(components)映射到路由(routes),而後告訴 vue-router 在哪裏渲染它們 起步vue-router
//*** router-link 告訴瀏覽器去哪一個路由 //*** router-view 告訴路由在哪裏展現內容 <div id="app"> <h1>Hello App!</h1> <p> <!-- 使用 router-link 組件來導航. --> <!-- 經過傳入 `to` 屬性指定連接. --> <!-- <router-link> 默認會被渲染成一個 `<a>` 標籤 --> <router-link to="/foo">Go to Foo</router-link> <router-link to="/bar">Go to Bar</router-link> </p> <!-- 路由出口 --> <!-- 路由匹配到的組件將渲染在這裏 --> <router-view></router-view> </div> // 1. 定義(路由)組件。 // 能夠從其餘文件 import 進來 const Foo = { template: '<div>foo</div>' } const Bar = { template: '<div>bar</div>' } // 2. 定義路由 // 每一個路由應該映射一個組件。 其中"component" 能夠是 // 經過 Vue.extend() 建立的組件構造器, // 或者,只是一個組件配置對象。 // 咱們晚點再討論嵌套路由。 const routes = [ { path: '/foo', component: Foo }, { path: '/bar', component: Bar } ]//歡迎加入前端全棧開發交流圈一塊兒學習交流:864305860 // 3. 建立 router 實例,而後傳 `routes` 配置 // 你還能夠傳別的配置參數, 不過先這麼簡單着吧。 const router = new VueRouter({ routes // (縮寫)至關於 routes: routes }) // 4. 建立和掛載根實例。 // 記得要經過 router 配置參數注入路由, // 從而讓整個應用都有路由功能 const app = new Vue({ router }).$mount('#app') // 如今,應用已經啓動了!
動態路由匹配編程
至關於同一個組件,由於參數不一樣展現不一樣的組件內容,其實就是在 vue-router 的路由路徑中使用『動態路徑參數』數組
const router = new VueRouter({ routes: [ // 動態路徑參數 以冒號開頭 { path: '/user/:id', component: User } ] })
那麼咱們進入uesr/001 和 user/002 實際上是進入的同一個路由,能夠根據參數的不一樣在內容頁展現不一樣的內容。通常適用場景:列表,權限控制 定義的時候用: 表示是動態路由瀏覽器
使用{{ $route.params.id }}
來拿到本路由裏面參數的內容 當使用路由參數時,例如從 /user/foo
導航到 /user/bar
,原來的組件實例會被複用。由於兩個路由都渲染同個組件,比起銷燬再建立,複用則顯得更加高效。不過,這也意味着組件的生命週期鉤子不會再被調用。 複用組件時,想對路由參數的變化做出響應的話,你能夠簡單地 watch(監測變化)$route
對象服務器
const User = { template: '...', watch: { '$route' (to, from) { // 對路由變化做出響應... }//歡迎加入前端全棧開發交流圈一塊兒學習交流:864305860 } }
有時候,同一個路徑能夠匹配多個路由,此時,匹配的優先級就按照路由的定義順序:誰先定義的,誰的優先級就最高。app
嵌套路由異步
在路由裏面嵌套一個路由
//路由裏面也會出現 <router-view> 這是嵌套路由展現內容的地方 const User = { template: ` <div class="user"> <h2>User {{ $route.params.id }}</h2> <router-view></router-view> </div> ` } //定義路由的時候在 加children 子路由屬性 const router = new VueRouter({ routes: [ { path: '/user/:id', component: User, children: [ { // 當 /user/:id/profile 匹配成功, // UserProfile 會被渲染在 User 的 <router-view> 中 path: 'profile', component: UserProfile },//歡迎加入前端全棧開發交流圈一塊兒學習交流:864305860 { // 當 /user/:id/posts 匹配成功 // UserPosts 會被渲染在 User 的 <router-view> 中 path: 'posts', component: UserPosts } ] } ] })
設置空路由,在沒有指定路由的時候就會展現空路由內容
const router = new VueRouter({ routes: [ { path: '/user/:id', component: User, children: [ // 當 /user/:id 匹配成功, // UserHome 會被渲染在 User 的 <router-view> 中 { path: '', component: UserHome }, ]//歡迎加入前端全棧開發交流圈一塊兒學習交流:864305860 } ] })
編程式導航
聲明式:<router-link :to="..."> 編程式:router.push(...) 能夠想象編程式 push 能夠理解爲向瀏覽器歷史裏面push一個新的hash,致使路由發生變化 router.replace() 修改路由可是不存在歷史裏面 router.go(n) 有點像JS的window.history.go(n) 命名路由 就是給每個路由定義一個名字。
命名視圖
有時候想同時(同級)展現多個視圖,而不是嵌套展現,例如建立一個佈局,有 sidebar(側導航) 和 main(主內容) 兩個視圖,這個時候命名視圖就派上用場了。你能夠在界面中擁有多個單獨命名的視圖,而不是隻有一個單獨的出口。若是 router-view 沒有設置名字,那麼默認爲 default。
<router-view class="view one"></router-view> <router-view class="view two" name="a"></router-view> <router-view class="view three" name="b"></router-view>
一個視圖使用一個組件渲染,所以對於同個路由,多個視圖就須要多個組件。確保正確使用 components 配置(帶上 s):
const router = new VueRouter({ routes: [ { path: '/', components: { default: Foo, a: Bar, b: Baz } } ] })//歡迎加入前端全棧開發交流圈一塊兒學習交流:864305860
重定向和別名
重定向也是經過 routes 配置來完成,下面例子是從 /a 重定向到 /b:
const router = new VueRouter({ routes: [ { path: '/a', redirect: '/b' } ] })
通常首頁的時候能夠重定向到其餘的地方 重定向的目標也能夠是一個命名的路由:
const router = new VueRouter({ routes: [ { path: '/a', redirect: { name: 'foo' }} ]//歡迎加入前端全棧開發交流圈一塊兒學習交流:864305860 })
甚至是一個方法,動態返回重定向目標:
const router = new VueRouter({ routes: [ { path: '/a', redirect: to => { // 方法接收 目標路由 做爲參數 // return 重定向的 字符串路徑/路徑對象 }} ] })
『重定向』的意思是,當用戶訪問 /a時,URL 將會被替換成 /b,而後匹配路由爲 /b,那麼『別名』又是什麼呢? /a 的別名是 /b,意味着,當用戶訪問 /b 時,URL 會保持爲 /b,可是路由匹配則爲 /a,就像用戶訪問 /a 同樣。 上面對應的路由配置爲:
const router = new VueRouter({ routes: [ { path: '/a', component: A, alias: '/b' } ]//歡迎加入前端全棧開發交流圈一塊兒學習交流:864305860 })
『別名』的功能讓你能夠自由地將 UI 結構映射到任意的 URL,而不是受限於配置的嵌套路由結構。
HTML5 History 模式
ue-router 默認 hash 模式 —— 使用 URL 的 hash 來模擬一個完整的 URL,因而當 URL 改變時,頁面不會從新加載。 若是不想要很醜的 hash,咱們能夠用路由的 history 模式,這種模式充分利用 history.pushState API 來完成 URL 跳轉而無須從新加載頁面。
const router = new VueRouter({ mode: 'history', routes: [...] })//歡迎加入前端全棧開發交流圈一塊兒學習交流:864305860
當你使用 history 模式時,URL 就像正常的 url,例如 http://yoursite.com/user/id,也好看!
不過這種模式要玩好,還須要後臺配置支持。由於咱們的應用是個單頁客戶端應用,若是後臺沒有正確的配置,當用戶在瀏覽器直接訪問 http://oursite.com/user/id 就會返回 404,這就很差看了。
因此呢,你要在服務端增長一個覆蓋全部狀況的候選資源:若是 URL 匹配不到任何靜態資源,則應該返回同一個 index.html 頁面,這個頁面就是你 app 依賴的頁面。
給個警告,由於這麼作之後,你的服務器就再也不返回 404 錯誤頁面,由於對於全部路徑都會返回 index.html 文件。爲了不這種狀況,你應該在 Vue 應用裏面覆蓋全部的路由狀況,而後在給出一個 404 頁面。
const router = new VueRouter({ mode: 'history', routes: [//歡迎加入前端全棧開發交流圈一塊兒學習交流:864305860 { path: '*', component: NotFoundComponent } ] })
或者,若是你使用 Node.js 服務器,你能夠用服務端路由匹配到來的 URL,並在沒有匹配到路由的時候返回 404,以實現回退。
導航守衛
個人理解 就是組件或者全局級別的 組件的鉤子函數 正如其名,vue-router 提供的導航守衛主要用來經過跳轉或取消的方式守衛導航。有多種機會植入路由導航過程當中:全局的, 單個路由獨享的, 或者組件級的。 記住參數或查詢的改變並不會觸發進入/離開的導航守衛。你能夠經過觀察 $route 對象來應對這些變化,或使用 beforeRouteUpdate 的組件內守衛。
全局守衛
const router = new VueRouter({ ... }) router.beforeEach((to, from, next) => { // ... })
每一個守衛方法接收三個參數: to: Route: 即將要進入的目標 路由對象 from: Route: 當前導航正要離開的路由 next: Function: 必定要調用該方法來 resolve 這個鉤子。執行效果依賴 next 方法的調用參數。 next(): 進行管道中的下一個鉤子。若是所有鉤子執行完了,則導航的狀態就是 confirmed (確認的)。 next(false): 中斷當前的導航。若是瀏覽器的 URL 改變了(多是用戶手動或者瀏覽器後退按鈕),那麼 URL 地址會重置到 from 路由對應的地址。 next(‘/') 或者 next({ path: ‘/' }): 跳轉到一個不一樣的地址。當前的導航被中斷,而後進行一個新的導航。你能夠向 next 傳遞任意位置對象,且容許設置諸如 replace: true、name: ‘home' 之類的選項以及任何用在 router-link 的 to prop 或 router.push 中的選項。 next(error): (2.4.0+) 若是傳入 next 的參數是一個 Error 實例,則導航會被終止且該錯誤會被傳遞給 router.onError() 註冊過的回調。 確保要調用 next 方法,不然鉤子就不會被 resolved。
全局後置鉤子
你也能夠註冊全局後置鉤子,然而和守衛不一樣的是,這些鉤子不會接受 next 函數也不會改變導航自己:
router.afterEach((to, from) => { // ... })\
路由獨享的守衛
你能夠在路由配置上直接定義 beforeEnter 守衛:
const router = new VueRouter({ routes: [ { path: '/foo', component: Foo, beforeEnter: (to, from, next) => { // ...//歡迎加入前端全棧開發交流圈一塊兒學習交流:864305860 } } ] })
這些守衛與全局前置守衛的方法參數是同樣的。
組件內的守衛
最後,你能夠在路由組件內直接定義如下路由導航守衛:
beforeRouteEnter beforeRouteUpdate (2.2 新增) beforeRouteLeave const Foo = { template: `...`, beforeRouteEnter (to, from, next) { // 在渲染該組件的對應路由被 confirm 前調用 // 不!能!獲取組件實例 `this` // 由於當守衛執行前,組件實例還沒被建立 },//歡迎加入前端全棧開發交流圈一塊兒學習交流:864305860 beforeRouteUpdate (to, from, next) { // 在當前路由改變,可是該組件被複用時調用 // 舉例來講,對於一個帶有動態參數的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉的時候, // 因爲會渲染一樣的 Foo 組件,所以組件實例會被複用。而這個鉤子就會在這個狀況下被調用。 // 能夠訪問組件實例 `this` }, beforeRouteLeave (to, from, next) { // 導航離開該組件的對應路由時調用 // 能夠訪問組件實例 `this` } }
beforeRouteEnter 守衛 不能 訪問 this,由於守衛在導航確認前被調用,所以即將登場的新組件還沒被建立。 完整的導航解析流程
導航被觸發。 在失活的組件裏調用離開守衛。 調用全局的 beforeEach 守衛。 在重用的組件裏調用 beforeRouteUpdate 守衛 (2.2+)。 在路由配置裏調用 beforeEnter。 解析異步路由組件。 在被激活的組件裏調用 beforeRouteEnter。 調用全局的 beforeResolve 守衛 (2.5+)。 導航被確認。 調用全局的 afterEach 鉤子。 觸發 DOM 更新。 用建立好的實例調用 beforeRouteEnter 守衛中傳給 next 的回調函數。
路由元信息
個人理解就是 他能夠把路由的父路徑都列舉出來,完成一些任務,好比登陸,user 組件須要登陸,那麼user下面的foo組件也須要,那麼能夠經過這個屬性 來檢測這個路由線上 的一些狀態。 定義路由的時候能夠配置 meta 字段:
const router = new VueRouter({ routes: [ {//歡迎加入前端全棧開發交流圈一塊兒學習交流:864305860 path: '/foo', component: Foo, children: [ { path: 'bar', component: Bar, // a meta field meta: { requiresAuth: true } } ] } ] })
首先,咱們稱呼 routes 配置中的每一個路由對象爲 路由記錄。路由記錄能夠是嵌套的,所以,當一個路由匹配成功後,他可能匹配多個路由記錄 例如,根據上面的路由配置,/foo/bar 這個 URL 將會匹配父路由記錄以及子路由記錄。 一個路由匹配到的全部路由記錄會暴露爲 $route 對象(還有在導航守衛中的路由對象)的 $route.matched 數組。所以,咱們須要遍歷 $route.matched 來檢查路由記錄中的 meta 字段。 下面例子展現在全局導航守衛中檢查元字段:
if (to.matched.some(record => record.meta.requiresAuth)) { // this route requires auth, check if logged in // if not, redirect to login page. if (!auth.loggedIn()) { next({//歡迎加入前端全棧開發交流圈一塊兒學習交流:864305860 path: '/login', query: { redirect: to.fullPath } }) } else { next() } } else { next() // 確保必定要調用 next() }//歡迎加入前端全棧開發交流圈一塊兒學習交流:864305860 })
數據獲取
個人理解就是在哪裏獲取數據,能夠再組件裏面,也能夠在組件的守衛裏面,也就是組件的生命週期裏面。 有時候,進入某個路由後,須要從服務器獲取數據。例如,在渲染用戶信息時,你須要從服務器獲取用戶的數據。咱們能夠經過兩種方式來實現: 導航完成以後獲取:先完成導航,而後在接下來的組件生命週期鉤子中獲取數據。在數據獲取期間顯示『加載中』之類的指示。 導航完成以前獲取:導航完成前,在路由進入的守衛中獲取數據,在數據獲取成功後執行導航。 從技術角度講,兩種方式都不錯 —— 就看你想要的用戶體驗是哪一種。
導航完成後獲取數據
當你使用這種方式時,咱們會立刻導航和渲染組件,而後在組件的 created 鉤子中獲取數據。這讓咱們有機會在數據獲取期間展現一個 loading 狀態,還能夠在不一樣視圖間展現不一樣的 loading 狀態。 假設咱們有一個 Post 組件,須要基於 $route.params.id 獲取文章數據:
<template> <div class="post"> <div class="loading" v-if="loading"> Loading... </div> <div v-if="error" class="error"> {{ error }} </div> <div v-if="post" class="content"> <h2>{{ post.title }}</h2> <p>{{ post.body }}</p> </div>//歡迎加入前端全棧開發交流圈一塊兒學習交流:864305860 </div> </template> export default { data () { return { loading: false, post: null, error: null } }, created () { // 組件建立完後獲取數據, // 此時 data 已經被 observed 了 this.fetchData() }, watch: { // 若是路由有變化,會再次執行該方法 '$route': 'fetchData' },//歡迎加入前端全棧開發交流圈一塊兒學習交流:864305860 methods: { fetchData () { this.error = this.post = null this.loading = true // replace getPost with your data fetching util / API wrapper getPost(this.$route.params.id, (err, post) => { this.loading = false if (err) { this.error = err.toString() } else {//歡迎加入前端全棧開發交流圈一塊兒學習交流:864305860 this.post = post } }) } } }
在導航完成前獲取數據
經過這種方式,咱們在導航轉入新的路由前獲取數據。咱們能夠在接下來的組件的 beforeRouteEnter 守衛中獲取數據,當數據獲取成功後只調用 next 方法。
export default { data () { return { post: null, error: null } }, beforeRouteEnter (to, from, next) { getPost(to.params.id, (err, post) => { next(vm => vm.setData(err, post)) })//歡迎加入前端全棧開發交流圈一塊兒學習交流:864305860 }, // 路由改變前,組件就已經渲染完了 // 邏輯稍稍不一樣 beforeRouteUpdate (to, from, next) { this.post = null getPost(to.params.id, (err, post) => { this.setData(err, post) next()//歡迎加入前端全棧開發交流圈一塊兒學習交流:864305860 }) }, methods: { setData (err, post) { if (err) { this.error = err.toString() } else { this.post = post }//歡迎加入前端全棧開發交流圈一塊兒學習交流:864305860 } } }
在爲後面的視圖獲取數據時,用戶會停留在當前的界面,所以建議在數據獲取期間,顯示一些進度條或者別的指示。若是數據獲取失敗,一樣有必要展現一些全局的錯誤提醒。
結語
感謝您的觀看,若有不足之處,歡迎批評指正。