寫在前面
使用者進行了互動操作,現在要對頁面内容進行變更,可以通過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.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.replace(location, onComplete?, onAbort?)
聲明式 | 程式設計式 |
---|---|
<router-link :to="..." replace> | router.replace(...) |
跟
router.push
很像,唯一的不同就是,它不會向 history 添加新記錄!
router.go(n)
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
}
}
]
})
複制
重定向和别名
重定向
// 方式一:字元串路徑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'
}
複制
向路由元件傳遞 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」
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
-
(2.2 新增)beforeRouteUpdate
-
beforeRouteLeave
需要注意的是beforeRouteEnter不能通路this,可以通過傳一個回調給
next
來通路元件執行個體。
beforeRouteEnter (to, from, next) {
next(vm => {
// 通過 `vm` 通路元件執行個體
})
}
複制
完整的導航解析流程
- 導航被觸發。
- 在失活的元件内調用離開守衛
。beforeRouteLeave
- 調用全局的
守衛。beforeEach
- 在重用的元件内調用
守衛 (2.2+)。beforeRouteUpdate
- 在路由配置裡調用獨享守衛
。beforeEnter
- 解析異步路由元件。
- 在被激活的元件内調用
。beforeRouteEnter
- 調用全局的
守衛 (2.5+)。beforeResolve
- 導航被确認。
- 調用全局的
鈎子。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();
}
複制
資料擷取
有時候,進入某個路由後,需要從伺服器擷取資料。
-
導航完成之後擷取:先完成導航,然後在接下來的元件生命周期鈎子中擷取資料。在資料擷取期間顯示『加載中』之類的訓示。
該方式會馬上導航和渲染元件,然後在元件的
鈎子中擷取資料。這讓我們有機會在資料擷取期間展示一個 loading 狀态,還可以在不同視圖間展示不同的 loading 狀态。created
-
導航完成之前擷取:導航完成前,在路由進入的守衛中擷取資料,在資料擷取成功後執行導航。
該方式在導航轉入新的路由前擷取資料。我們可以在接下來的元件内的
守衛中擷取資料,當資料擷取成功後隻調用beforeRouteEnter
方法。next
滾動行為
隻在 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.path、route.params、route.query、route.query、route.hash、route.fullPath、route.fullPath、route.matched
重點強調:this.router.currentRoute===this.router.currentRoute === this.route
行為表現
因為它也是個元件,是以可以配合
<transition>
和
<keep-alive>
使用。如果兩個結合一起用,要確定在内層使用
<keep-alive>
:
<transition>
<keep-alive>
<router-view></router-view>
</keep-alive>
</transition>
複制
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
}
}
}
}
複制
sidebar編寫
基本思路和header相似,唯一差別是要根據目前一級路由進行鋪值!