一、簡介
Vue-router 是 Vue.js
官方的路由管理器。它和 Vue.js
的核心深度集成,讓構建單頁面應用變得易如反掌
先來了解兩點
-
單頁面應用(SPA)
-
1.1 單頁面應用
單頁面應用程序將所有的活動局限於一個Web頁面中,僅在該Web頁面初始化時加載相應的HTML、JavaScript 和 CSS。一旦頁面加載完成,SPA不會因為用戶的操作而進行頁面的重新加載或跳轉。取而代之的是利用 JavaScript 動態的變換HTML的內容,從而實現UI與用戶的交互。
1.2 路由管理器
這里的路由管理器並不是我們平時生活中的硬件路由器,這里的路由就是單頁應用(SPA)的路徑管理器,就是為了解決Vue.js開發單頁面應用不能進行鏈接跳轉,我們通過路徑的的方式來管理不同的頁面
了解Vue-router所解決的問題之后, 我們開始學習Vue-router在項目中常用的一些功能:
-
嵌套的路由/視圖表
-
模塊化的、基於組件的路由配置
-
路由參數、查詢、通配符
-
細粒度的導航控制
二、起步
在開始我們先體會下Vue-router的一些功能:
-
動態路由匹配
-
嵌套路由
-
聲明式/編程式導航
-
命名路由/命名視圖
-
重定向和別名
-
路由組件傳參
2.1 動態路由匹配
router.js
import Vue from 'vue' // 引入Vue import Router from 'vue-router' // 引入vue-router import Home from '@/pages/Home' //引入根目錄下的Hello.vue組件 // Vue全局使用Router Vue.use(Router) /*使用 Vue.js + Vue-router構建單頁面應用, 只要通過組合組件來組成我們的應用程序, 我們引入Vue-router,只要將組件映射到路由,告訴Vue-router在那里渲染它們 */ let routes = [ // 配置路由,這里是個數組 { // 每一個鏈接都是一個對象 path: '/', // 鏈接路徑 name: 'Home', // 路由名稱, component: Home // 對應的組件模板 }, // 動態路徑參數 以冒號開頭 { path: '/user/:username', // 動態路由 component: () => import('../pages/User1'), // 按需加載路由對應的組件, 需要下載polyfill兼容ES6語法 }, { // 多段路徑參數 path: '/user/:id/post/:post_id', // 動態路由 component: () => import('../pages/User2'), // 按需加載路由對應的組件, 需要下載polyfill兼容ES6語法 }, ] export default new Router({ routes })
User1 用戶訪問 /#/user/xxx的時候展示該組件 <template> <div class="User1"> User1 - 單個路徑參數 </div> </template> User2 用戶訪問 /#/user/xxx/post/xxx的時候展示該組件 <template> <div class="User2"> User2 - 多段路徑參數路由 </div> </template>
那么問題來了,我們怎么知道用戶訪問的是那個動態參數路由呢?這個時候就要用到響應路由參數的變化
兩種方式:watch (監測變化) $route
對象, beforeRouteUpdate
導航守衛
向user1.vue
增加下面代碼
<template> <div class="User1"> <!-- 通過router對象可以獲取到路由屬性, 這種方式耦合了,后面會講路由組件傳參的方式 --> User1 -{{$route.params.username}} 單個路徑參數 </div> </template> <script> export default { name: 'User1', // 偵聽route對象方式 watch: { $route (to, from) { this.$message.success(`watch -> ${to.path}, ${from.path}`) }, }, // vue2.2引入的導航守衛,當路由參數發生變化調用 beforeRouteUpdate (to, from, next) { this.$message.info(`導航守衛 -> ${to.path}, ${from.path}`) // 一定要調用next讓其繼續解析下一個管道中的路由組件 next() } } </script>
2.2 路由組件傳參
上面在<tempate>
模板中通過$router.prarams.username
方式獲取路由傳遞的參數已經於其對應路由形成高度耦合,限制了其靈活性, 我們可以通過props
將組件和路由進行解耦
props傳遞路由組件參數有三種方式:
-
布爾模式
-
對象模式
-
函數模式
代碼
router.js
import Vue from 'vue' import Router from 'vue-router' import home from '@/pages/Home' Vue.use(Router) let routes = [ { path: '/', name: 'Home', component: home }, { // 動態路徑參數 以冒號開頭 path: '/user1/:username', // 動態路由 component: () => import('../pages/User1'), props: true // 布爾模式: 如果 props 被設置為 true,route.params 將會被設置為組件屬性。 }, { path: '/user2', component: () => import('../pages/User2'), props: {username: 'ck'} // 對象模式: 只有靜態路由才能有效, 並且參數是寫死的 }, { path: '/user3/:username', component: () => import('../pages/User3'), // 返回了用戶url中的參數 比如 /user3?username='ck' => {username: 'ck} 最終還是以對象模式的方式返回參數 props: (route) => ({username: route.query.username}) // 函數模式 } ] export default new Router({ routes })
User1 布爾模式 <template> <div class="User1"> User1 -{{username}} </div> </template> <script> export default { name: 'User1', props: ['username'] // 通過props獲取路由傳遞給對應組件的參數 } </script>
User2 對象模式 <template> <div class="User2"> User2 - {{username}} </div> </template> <script> export default { name: 'User2', props: ['username'] // 通過props獲取路由傳遞給對應組件的參數 } </script>
User3 函數模式 <template> <div class="User3"> User3 - {{username}} </div> </template> <script> export default { name: 'User3', props: ['username'] // 通過props獲取路由傳遞給對應組件的參數 } </script>
演示
從上面我們可以看出因為user2是靜態路由所以不支持動態參數而且其對應的路由組件傳遞過來的參數也是寫死的
2.3 嵌套路由
實際生活中的應用界面,通常由多層嵌套的組件組合而成。同樣地,URL 中各段動態路徑也按某種結構對應嵌套的各層組件,例如:
router.js
import Vue from 'vue' import Router from 'vue-router' import home from '@/pages/Home' Vue.use(Router) let routes = [ { path: '/', name: 'Home', component: home, }, { path: '/user/:username', // 動態路由 name: 'User', component: () => import('../components/User'), children: [ { // 當 '/user/:username/profile' 匹配成功, UserProfile 會被渲染在 User 的 <router-view> 中 path: 'profile', // 可以匹配 /user/ks/profile name: 'Profile', component: () => import('../components/Profile') }, { path: '/user/:usrname/posts', // 這樣也可以匹配 /user/ks/posts, 但其實是將其匹配為了根組件的/user:username動態組件下的 posts name: 'Posts', component: () => import('../components/Posts') }, { path: '', name: 'UserHome', // 當 /user/:username 匹配成功,比如 /user/ks || /user/ck // UserHome 會被渲染在 User 的 <router-view> 中 component: () => import('../components/UserHome') }, ] }, { path: '/footer', name: 'Foo', component: () => import('../components/Footer') } ] export default new Router({ routes })
演示
聲明式/編程式導航
聲明式 | 編程式 |
---|---|
<router-link :to="..." replace> |
router.replace(...) |
<template> <div class="home"> <!-- 聲明式 --> <router-link to="footer" tag="button" > to footer </router-link> <!-- 編程式 --> <button @click="$router.push('footer')">字符串-寫路由路徑的方式</button> <button @click="$router.push({path: '/footer'})">對象-寫路徑的方式</button> <button @click="$router.push({name: 'Foo', params: {'userId': '123'}})">name和params - 寫路由名稱攜帶參數的方式</button> <button @click="$router.push({path: '/footer', query: {'userId': '456'}})">queyr和path - 寫路由路徑攜帶參數的方式</button> <router-view></router-view> </div> </template> <script> export default { name: 'home', data () { return { } }, methods: { } } </script> <style> button { display: block; } </style>
-
router.push(location, onComplete?, onAbort?)
-
router.replace(location, onComplete?, onAbort?)
這兩種的方式一樣, 唯一區別在於 push
會產生路由的歷史記錄, 而repalce
不會產生, 這對於window中的history
是一致的
<!-- router.go方法 --> <template> <button @click="goForward">go(1)-前進一步</button> <button @click="goBack">go(-1)-后退一步</button> <button @click="gogogo">go(100)-前進一白步</button> </template> <script> export default { name: 'home' methods: { goForward () { // 從歷史路由中前進一步相當於 window.history.forward this.$router.go(1); }, goBack () { // 從歷史路由中后退一步相當於 window.history.back this.$router.go(-1); }, gogogo () { // 歷史路由中沒有100步, 就啥也不干 this.$router.go(100); } } } </script>
演示
命名路由/命名視圖/重定向和別名
router.js
import Vue from 'vue' import Router from 'vue-router' import UserSettings from '@/pages/UserSettings' Vue.use(Router) let routes = [ { path: '/', redirect: '/settings' // 重定向 }, { path: '/settings', name: 'Settings', // 命名路由 alias: '/a', // 取別名,當url中訪問 /a -> 也是訪問的 settings組件但是路由匹配的是/a, 就相當於用戶訪問 /a一樣 // 你也可以在頂級路由就配置命名視圖 component: UserSettings, children: [ { path: 'emails', component: () => import('../pages/UserEmails') }, { path: 'profile', components: { default: () => import('../pages/UserProfile'), helper: () => import('../pages/UserProfilePreview') } } ] } ] export default new Router({ routes })
UserSetttings
<template> <div class="UserSettings"> <h1>User Settings</h1> <NavBar/> <router-view/> <!-- 命名視圖 --> <router-view name="helper"/> </div> </template> <script> import NavBar from '../components/NavBar' export default { name: 'UserSettings', components: { NavBar } } </script>
通過上面的學習相信大家已經撐握了Vue-router在項目中所常用的功能,下面我們開始學習Vue-router的導航守衛
三、進階
導航守衛
“導航”表示路由正在發生改變。記住參數或查詢的改變並不會觸發進入/離開的導航守衛。你可以通過觀察
$route
對象響應路由參數的變化來應對這些變化,或使用beforeRouteUpdate
的組件內守衛。
全局的守衛
-
全局前置守衛 (router.beforeEach)
-
全局解析守衛 (router.breforeResolve)
-
全局后置鈎子 (router.afterEach) 注:這個鈎子中不存在next
路由獨享的守衛
你可以在路由配置上直接定義 beforeEnter
守衛:
const router = new VueRouter({ routes: [ { path: '/foo', component: Foo, beforeEnter: (to, from, next) => { // to -> 要跳轉過去的路由信息 // from -> 當前的路由信息 // next() => 一個函數,表示解析下一個管道中的路由記錄 } } ] })
組件內的守衛
最后,你可以在路由組件內直接定義以下路由導航守衛:
-
beforeRouteEnter
-
beforeRouteUpdate
(2.2 新增) -
beforeRouteLeave
const Foo = { template: `...`, beforeRouteEnter (to, from, next) { // 在渲染該組件的對應路由被 confirm 前調用 // 不!能!獲取組件實例 `this` // 因為當守衛執行前,組件實例還沒被創建 }, beforeRouteUpdate (to, from, next) { // 在當前路由改變,但是該組件被復用時調用 // 舉例來說,對於一個帶有動態參數的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉的時候, // 由於會渲染同樣的 Foo 組件,因此組件實例會被復用。而這個鈎子就會在這個情況下被調用。 // 可以訪問組件實例 `this` }, beforeRouteLeave (to, from, next) { // 導航離開該組件的對應路由時調用 // 可以訪問組件實例 `this` } }
beforeRouteEnter
守衛 不能 訪問 this
,因為守衛在導航確認前被調用,因此即將登場的新組件還沒被創建。不過,你可以通過傳一個回調給 next
來訪問組件實例。在導航被確認的時候執行回調,並且把組件實例作為回調方法的參數。
beforeRouteEnter (to, from, next) { next(vm => { // 在實例創建好之后會調用next傳遞過去的回調並且將實例當做參數傳遞進來,所以通過 `vm` 可以訪問組件實例 }) }
這個離開守衛通常用來禁止用戶在還未保存修改前突然離開。該導航可以通過 next(false)
來取消。】
beforeRouteLeave (to, from, next) { const answer = window.confirm('Do you really want to leave? you have unsaved changes!') if (answer) { next() } else { next(false) } }