寫在前面
用戶進行了交互操作,現在要對頁面內容進行變更,此時可以通過javascript進行動態替換DOM,但是其不便於分享、收藏,對於搜索引擎和用戶來說都是不友好的!
什麼是前端路由?
根據不同的 url 地址展示不同的內容或頁面,無需依賴服務器根據不同URL進行頁面展示操作
優點
- 用戶體驗好,不需要每次都從服務器全部獲取,快速展現給用戶
缺點
- 使用瀏覽器的前進,後退鍵的時候會重新發送請求,沒有合理地利用緩存
- 單頁面無法記住之前滾動的位置,無法在前進,後退的時候記住滾動的位置
簡介
使用 Vue.js ,可以通過組合組件來組成應用程序,當你要把 vue-router 添加進來,我們需要做的是,將組件(components)映射到路由(routes),然後告訴 vue-router 在哪裏渲染它們。
// 1. 定義、引用(路由)組件。
const Foo = { template: '<div>foo</div>' }
import Bar from '@/views/bar.vue'
// 2. 定義路由
const routes = [
{ path: '/foo', name: 'foo', component: Foo },
{ path: '/bar', name: 'bar', component: Bar }
]
// 3. 創建 router 實例,然後傳 `routes` 配置
const router = new VueRouter({
routes // (縮寫)相當於 routes: routes
})
// 4. 創建和掛載根實例。通過 router 配置參數注入路
const app = new Vue({
router
}).$mount('#app')
動態路由匹配
兩種方式傳遞$route.params
和$route.query
模式 | 匹配路徑 | 獲取參數(路由信息對象) |
---|---|---|
/user/:username | /user/ligang | $route.params.username |
/user?:username | /user?username=ligang | $route.query.username |
響應路由參數的變化
當使用路由參數時,例如從 /user/ligang
導航到 user/lg
,原來的組件實例會被複用。因爲兩個路由都渲染同個組件,比起銷燬再創建,複用則顯得更加高效。不過,這也意味着組件的生命週期鉤子不會再被調用。
方式一:簡單地watch(監測變化)$route對象
watch: {
'$route' (to, from) {
// 對路由變化作出響應...
}
}
方式二:使用 2.2 中引入的 beforeRouteUpdate
守衛
beforeRouteUpdate (to, from, next) {
// 對路由變化作出響應...不要忘記調用next()
}
示例:新增和編輯使用同一模塊,從編輯切換到新增頁面信息不會更新!
{
path: 'add',
name: 'setting-user-manager-add',
component: () => import('@/views/setting/user-manager/add-edit.vue'),
meta: {name: '用戶新增'}
}, {
path: 'edit',
name: 'setting-user-manager-edit',
component: () => import('@/views/setting/user-manager/add-edit.vue'),
meta: {
name: '用戶編輯',
hidden: true
}
}
嵌套路由
routes: [{
path: '/user/:id',
component: User,
children: [
// 匹配 /user/:id
{ path: '', component: UserHome },
// 匹配 /user/:id/profile
{ path: 'profile', component: UserProfile },
// 匹配 /user/:id/posts
{ path: 'posts', component: UserPosts }
]
}]
要注意,以 / 開頭的嵌套路徑會被當作根路徑。 這讓你充分的使用嵌套組件而無須設置嵌套的路徑。
編程式導航
router.push(location, onComplete?, onAbort?)
聲明式 | 編程式 |
---|---|
<router-link :to="..."> |
router.push(...) |
// 方式一:字符串路徑
router.push('/user')
// 方式二:path對象
router.push({ path: '/user' })
// 方式三:路由名稱
router.push({ name: 'user'})
注意:如果提供了 path,params 會被忽略,query不會!!
// 不生效
router.push({ path: '/user', params: { id: 1 }})
// params生效 /user/1
router.push({ name: 'user', params: { id: 1 }}) // 使用name方式
router.push({ path: `/user/1` }) // 直接在path上擴充
// query不受影響 /user?id=1
router.push({ path: '/user', query: { id: 1 }})
router.replace(location, onComplete?, onAbort?)
聲明式 | 編程式 |
---|---|
<router-link :to="..." replace> |
router.replace(...) |
跟router.push
很像,唯一的不同就是,它不會向 history 添加新記錄!
router.go(n)
在 history 記錄中向前或者後退多少步,類似 window.history.go(n)
命名視圖
多個非嵌套視圖展示,例如創建一個佈局,有header
頭信息、 sidebar
(側導航) 和 main
(主內容) 兩個視圖。
<router-view class="view header" name="header"></router-view>
<router-view class="view sidebar" name="sidebar"></router-view>
<router-view class="view main"></router-view>
const router = new VueRouter({
routes: [
{
path: '/',
components: {
default: mainComponent,
sidebar: sidebarComponent,
header: headerComponent
}
}
]
})
gsp中header和sidebar也可以通過這種方式
重定向和別名
重定向
// 方式一:字符串路徑path
{ path: '/a', redirect: '/b' }
// 方式二:name
{ path: '/a', redirect: {name: 'b'} }
// 方式三:動態返回重定向目標
{ path: '/a', redirect: to => {
/* 方法接收 目標路由 作爲參數;return 重定向的 字符串路徑/路徑對象 */
}}
別名
/a
的別名是/b
,意味着當用戶訪問/b
時,URL會保持爲/b
,但是路由匹配則爲/a
,就像用戶訪問/a
一樣。
{ path: '/a', component: A, alias: '/b' }
『別名』的功能讓你可以自由地將 UI 結構映射到任意的 URL,而不是受限於配置的嵌套路由結構。
示例:上述【動態路由匹配】可修改成如下,可能存在name問題
{
path: 'add',
name: 'setting-user-manager-add',
component: () => import('@/views/setting/user-manager/add-edit.vue'),
meta: {name: '用戶新增'},
alias: 'edit'
}
思考:上述add、edit使用別名是否更好!
向路由組件傳遞 props
路由組件傳參
默認(常規)方式:通過$route.params獲取
const User = {
template: '<div>User {{ $route.params.id }}</div>'
}
const router = new VueRouter({
routes: [{ path: '/user/:id', component: User }]
})
使用props解耦:只需要將props設置爲true
const User = {
props: ['id'],
template: '<div>User {{ id }}</div>'
}
const router = new VueRouter({
routes: [{ path: '/user/:id', component: User, props: true }]
})
注意:上述props不僅可以設置爲布爾值,還可以設置爲對象或函數,具體請查看:「https://router.vuejs.org/zh-cn/essentials/passing-props.html」
poc中使用params傳值,是不是又多了一種獲取方式?
HTML5 History 模式
const router = new VueRouter({
mode: 'history',
routes: [...]
})
需要後臺配置,否則輸入的除首頁外都爲404(當然系統內跳轉可以)。具體ngix、Apache、node等配置參考:「https://router.vuejs.org/zh-cn/essentials/history-mode.html」
這裏說一下本地webpack需要增加的配置情況:historyApiFallback: true
「https://doc.webpack-china.org/configuration/dev-server/#devserver-historyapifallback」vue-cli生成的默認webpack配置,
historyApiFallback: {
rewrites: [
{ from: /.*/, to: path.join(config.dev.assetsPublicPath, 'index.html') },
],
}
在window下特定node版本會有問題!
導航守衛
『導航』表示路由正在發生改變
導航守衛主要用來通過跳轉或取消的方式守衛導航。注意參數或查詢的改變並不會觸發進入/離開的導航守衛。可以通過觀察 $route
對象來應對這些變化,或使用 beforeRouteUpdate
的組件內守衛。
@上述哪個地方提到?
全局守衛
使用 router.beforeEach
註冊一個全局前置守衛
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
守衛是異步解析執行,此時導航在所有守衛 resolve 完之前一直處於 等待中。所以確保要調用 next 方法,否則鉤子就不會被 resolved。
登錄攔截處理!僞代碼
全局解析守衛
在 2.5.0+ 你可以用 router.beforeResolve
註冊一個全局守衛。這和 router.beforeEach
類似,區別是在導航被確認之前,同時在所有組件內守衛和異步路由組件被解析之後,解析守衛就被調用。
@是否可以解決異步同步差異化的問題!
全局後置鉤子
你也可以註冊全局後置鉤子,然而和守衛不同的是,這些鉤子不會接受 next
函數也不會改變導航本身:
router.afterEach((to, from) => {
// ...
})
路由獨享的守衛
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
組件內的守衛
beforeRouteEnter
beforeRouteUpdate
(2.2 新增)beforeRouteLeave
需要注意的是beforeRouteEnter不能訪問this,可以通過傳一個回調給 next
來訪問組件實例。
beforeRouteEnter (to, from, next) {
next(vm => {
// 通過 `vm` 訪問組件實例
})
}
完整的導航解析流程
- 導航被觸發。
- 在失活的組件內調用離開守衛
beforeRouteLeave
。 - 調用全局的
beforeEach
守衛。 - 在重用的組件內調用
beforeRouteUpdate
守衛 (2.2+)。 - 在路由配置裏調用獨享守衛
beforeEnter
。 - 解析異步路由組件。
- 在被激活的組件內調用
beforeRouteEnter
。 - 調用全局的
beforeResolve
守衛 (2.5+)。 - 導航被確認。
- 調用全局的
afterEach
鉤子。 - 觸發 DOM 更新。
- 用創建好的實例調用
beforeRouteEnter
守衛中傳給next
的回調函數。
路由元信息
meta
字段來設置名稱、是否需要驗證、是否隱藏等附加信息!!
一個路由匹配到的所有路由記錄會暴露爲 $route
對象(還有在導航守衛中的路有對象)的 $route.matched
數組。因此,我們需要遍歷 $route.matched
來檢查路由記錄中的 meta
字段。
if (to.meta.requireAuth) { // 判斷該路由是否需要登錄權限
if (store.state.token) { // 通過vuex state獲取當前的token是否存在
next();
}else {
next({
path: '/login',
query: {redirect: to.fullPath} // 將跳轉的路由path作爲參數,登錄成功後跳轉到該路由
})
}
}else {
next();
}
數據獲取
有時候,進入某個路由後,需要從服務器獲取數據。
-
導航完成之後獲取:先完成導航,然後在接下來的組件生命週期鉤子中獲取數據。在數據獲取期間顯示『加載中』之類的指示。
該方式會馬上導航和渲染組件,然後在組件的
created
鉤子中獲取數據。這讓我們有機會在數據獲取期間展示一個 loading 狀態,還可以在不同視圖間展示不同的 loading 狀態。 -
導航完成之前獲取:導航完成前,在路由進入的守衛中獲取數據,在數據獲取成功後執行導航。
該方式在導航轉入新的路由前獲取數據。我們可以在接下來的組件內的
beforeRouteEnter
守衛中獲取數據,當數據獲取成功後只調用next
方法。
使用第二種方式會有什麼問題呢?腦洞~~~
beforeRouteEnter會有什麼問題呢???
滾動行爲
**只在 HTML5 history 模式下可用。**當切換到新路由時,想要頁面滾到頂部,或者是保持原先的滾動位置,就像重新加載頁面那樣。 vue-router
能做到,而且更好,它讓你可以自定義路由切換時頁面如何滾動。
const router = new VueRouter({
routes: [...],
scrollBehavior (to, from, savedPosition) {
// return 期望滾動到哪個的位置
}
})
參考地址:「https://router.vuejs.org/zh-cn/advanced/scroll-behavior.html」
特別說明
Router 實例
屬性 | 說明 |
---|---|
router.app | router 的 Vue 根實例 |
router.mode | 路由使用的模式 |
router.currentRoute | 當前路由對應的路由信息對象 |
方法:router.beforeEach(guard)、router.beforeResolve(guard) 、router.afterEach(hook)、router.push(location, onComplete?, onAbort?)、router.replace(location, onComplete?, onAbort?)、router.go(n)、router.back()、router.forward()、router.getMatchedComponents(location?)、router.resolve(location, current?, append?)、router.addRoutes(routes)、router.onReady(callback, [errorCallback])、router.onError(callback)
路由信息對象
每次成功的導航後都會產生一個新的對象
-
在組件內,即
this.$route
-
在
$route
觀察者回調內 -
router.match(location)
的返回值 -
導航守衛的參數:
router.beforeEach((to, from, next) => { // to 和 from 都是 路由信息對象 })
-
scrollBehavior
方法的參數:const router = new VueRouter({ scrollBehavior (to, from, savedPosition) { // to 和 from 都是 路由信息對象 } })
其包含的屬性值:
$route.path
、$route.params
、$route.query
、$route.hash
、$route.fullPath
、$route.matched
重點強調:this.route
行爲表現
因爲它也是個組件,所以可以配合 <transition>
和 <keep-alive>
使用。如果兩個結合一起用,要確保在內層使用 <keep-alive>
:
<transition>
<keep-alive>
<router-view></router-view>
</keep-alive>
</transition>
Keep-alive說明一下!!!
router-link
<router-link>
比起寫死的 <a href="...">
會好一些,理由如下:
-
無論是 HTML5 history 模式還是 hash 模式,它的表現行爲一致,所以,當你要切換路由模式,或者在 IE9 降級使用 hash 模式,無須作任何變動。
-
在 HTML5 history 模式下,
router-link
會守衛點擊事件,讓瀏覽器不再重新加載頁面。 -
當你在 HTML5 history 模式下使用
base
選項之後,所有的to
屬性都不需要寫(基路徑)了。base相關說明:「https://router.vuejs.org/zh-cn/api/options.html#base」
實例
header編寫
第一步: 獲取router的全部配置信息import {ROUTES} from '@/app.router'
,然後循環鋪值(獲取一級的路由)meta.name
第二步: 選擇header,路由跳轉;主要思路:在一級組件上配置meta.defaultRouteName
信息,獲取該信息後,進行調整(如果不含有該信息,則默認第一個子路由)
第三步: 處理當前選中的的header項目
watch: {
'$route': {
// 必須,解決路由同步加載組件時,$watch首次不執行的問題
immediate: true,
handler (to) {
if (to.matched[0]) {
this.currentRoute = to.matched[0].name
}
}
}
}