Vue路由-動態(tài)路由-編程式導航

? 通常我們會用一個或多個路由表,來匹配所有頁面的路徑。但這不能滿足一些特定的需求。

# 解惑

? 好幾次被問到怎么區(qū)分$route$router,這里統(tǒng)一給出解釋:

  • this.$router:指的是router實例蜂筹,也就是new VueRouter()的執(zhí)行結果需纳,使用效果與router相同。有這種寫法是因為Vue并不想在每個獨立需要封裝路由的組件中都導入路由艺挪,因此在全局創(chuàng)建了該對象不翩。
  • this.$route:指的是當前 激活路由信息對象,又稱路由記錄麻裳,該對象是只讀的口蝠,內部屬性不可改變,當路由發(fā)生重定向或路由參數(shù)改變時津坑,該對象會被重新計算妙蔗。一般我們會通過watch來監(jiān)聽它們。
    ?

# 基本路由

? 對于基本路由疆瑰,我們需要精準的匹配路由內容眉反,才能實現(xiàn)頁面跳轉昙啄。其編寫風格大致如下

import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import 404 from '@/404'
Vue.use(Router)

const router =  new Router({
    routes: [
        { path: '/', redirect: '/index' },
        { path: '/index', component: HelloWorld, alias: '/home' },
        { path: '/404', meta: { title: '錯誤頁面' }, component: 404 },
        { path: '/b', redirect: to => {
          // 接收目標路由to作為參數(shù),return值作為動態(tài)響應的路徑
        } }
    ]
})

export default router

? 在這個案例中寸五,this.$router就是router常量梳凛,而this.$route是在實例化后,即將登場的那個路由對象梳杏,如假設為{ path: '/index'韧拒,component: HelloWorld }
? meta 為路由元信息十性,常提供給 導航守衛(wèi) 和this.$route 使用
? alias是路由別名叛溢,其作用是當匹配到/home時,訪問的是/home路由但真正走的是/index路由的頁面

import為何能實現(xiàn)路由懶加載

? 曾經(jīng)我也困惑過作為編譯時調用的import是如何實現(xiàn)懶加載的烁试。其實對比
?import('./index.vue')(編譯時加載)和
?const Index = () => import('./index.vue')(懶加載)
? 簡單理解:后者編譯時只是聲明了函數(shù)雇初,只有使用時才運行該內容。
? 深入一些:利用Promise 可以將異步組件定義為返回一個Promise 的工廠函數(shù)减响,又因為webpack2中動態(tài)import能實現(xiàn)代碼分塊靖诗,從而達到定義一個能被Webpack自動代碼分割的異步組件的效果
?

# 動態(tài)路由匹配

? 例如,我們有一個 user 組件支示,對于所有ID各不相同的用戶刊橘,都要使用這個組件來渲染,因為用戶ID未知颂鸿,不可能再路由表里書寫這個路由促绵,這時,就需要使用到 vue-router 中的 “動態(tài)路徑參數(shù)” 來達到這個效果

const router = new VueRouter({
  routes: [
    // 動態(tài)路徑參數(shù)嘴纺,以冒號開頭
    { path: '/user/:id', component: () => import('@/components/User') }
  ]
})

? 現(xiàn)在呢败晴,對于如/user/12345/user/e9e0jh830d 類型的路由,都會被映射到相同的路由路徑上栽渴,而 /user/ 后的則是本路徑的參數(shù)尖坤,頁面初始化時并不關心它是什么內容,認為這是提供給頁面內部使用的一個參數(shù)

? 既然是參數(shù)闲擦,頁面就應該有能力獲取慢味,通常我們需要根據(jù)這個參數(shù)發(fā)起請求獲取頁面更詳細的渲染數(shù)據(jù),獲取參數(shù)的辦法如下

  • 單個參數(shù)
    Path:{ path: '/user/:id' }
    Url:/user/e9e0jh830d
const para = this.$route.params 
// { id: e9e0jh830d}
  • 支持多個參數(shù)
    Path: { path: 'user/:username/post/:post_id' }
    Url: /user/zhangfs/post/330473
const para = this.$route.params 
// { username: zhangfs, post_id: '330473' }

?

# 響應路由參數(shù)的變化!!!

? 有個很經(jīng)典的問題:假設在一個 User 列表中墅冷,用戶切換查看不同賬戶信息纯路,Url上/user/:id后面的 id 發(fā)生了變化實現(xiàn)了用戶切換,然而頁面本該展示不同用戶信息的數(shù)據(jù)并沒有更新寞忿。
? 原因是Vue在設計初衷驰唬,為了最大化高效利用組件,并不會銷毀后再創(chuàng)建,也就是說定嗓,組件的生命鉤子如Created將不會再次被觸發(fā)調用蜕琴,這就要求我們自己實現(xiàn)觸發(fā)。筆者提供4種思路
(1)監(jiān)聽路由參數(shù)宵溅,發(fā)生變化則重新獲取頁面渲染數(shù)據(jù)
(2)監(jiān)聽路由(Vue推薦)

watch: {
  '$route' (to, from) { ... } // to-from 都能監(jiān)聽出變化做出響應
}

(3)使用組件內導航守衛(wèi) beforeRouteUpdate

beforeRouteUpdate (to, from, next) { ... } // 別忘記調用next()

(4)為<router-view>增加唯一的key凌简。這種方式是逆Vue思路來的,設計Key關鍵字實現(xiàn)銷毀組件重新掛載來保證生命鉤子重新觸發(fā)

<router-view :key="key" />

computed: {
  key() {
    return this.$route.name ? this.$route.name + new Date() : this.$route + new Date()
  }
}

?

# 嵌套的子路由

? 通常恃逻,在app中聲明的路由為 頂級路由雏搂,作為頂層的出口,渲染最高級路由匹配到的組件寇损。

<div id="app">
  <router-view></router-view>
</div>
const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User }
  ]
})

? 如果需要實現(xiàn)子路由凸郑,如匹配 /user/:id/posts,則需要設置嵌套的路由矛市,使用children屬性定義

const router = new VueRouter({
  routes: [
    { 
      path: '/user/:id',
      component: User,
      children: [
        {
          // 當 /user/:id/profile 匹配成功芙沥,
          // UserProfile 會被渲染在 User 的 <router-view> 中
          path: 'profile',
          component: UserProfile
        },
        {
          // 當 /user/:id/posts 匹配成功
          // UserPosts 會被渲染在 User 的 <router-view> 中
          path: 'posts',
          component: UserPosts
        }
      ]
    }
  ]
})

注意】以/開頭的路徑會被當做根路徑。也就是說允許你充分的使用嵌套組件而無需設置嵌套的路徑

? 該例中浊吏,當遇到訪問/user的路徑不會渲染任何東西而昨,是因為沒有匹配到任何合適的子路由。當然你可以提供一個空的子路由來承接該路徑

children: [
  { path: '', component: UserHome }
]

?

# 編程式導航

? 我們知道<router-link>實際上是創(chuàng)建了a標簽來實現(xiàn)導航鏈接找田,我們還可以借助 router 的實例方法通過編寫代碼來實現(xiàn)歌憨。注意,如在開篇描述墩衙,組件內部應該使用this.$router訪問

# this.$router.push()
聲明式 編程式
<router-link :to="..." /> this.$router.push(...)

使用語法: this.$router.push(location, onComplete回調. onAbort回調)
? location接受一個字符串路徑务嫡,或一個描述地址的對象

this.$router.push('dashboard') // -> /dashboard
this.$router.push({ path: 'dashboard' }) // -> /dashboard
this.$router.push({ name: 'user', parmas: { userId: '123' } }) // -> /user/123
this.$router.push({ path: 'register', query: { plan: 'private' } }) // -> /register?plan=private

】注意觀察第三個和第四個,重定向帶路徑參數(shù)的路由時漆改,不能指定 path心铃,因為提供了 pathparams參數(shù)會自動被忽略。如果想要用path則需要修改如下:

const userId = '123'
this.$router.push({ path: `/user/${userId}` }) // -> /user/123
// 錯誤示例
this.$router.push({ path: `/user`, params: { userId: '123' } }) // -> /user

? 同樣的規(guī)則適用于 <router-link :to="">to 屬性
?

# this.$router.replace()
聲明式 編程式
<router-link :to="..." replace/> this.$router.replace(...)

? 跟 router.push 很像挫剑,唯一的不同就是去扣,它不會向 history 添加新記錄,而是跟它的方法名一樣 —— 替換掉當前的 history 記錄暮顺。
?

# this.$router.go(n)

? 這個方法的參數(shù)是一個整數(shù),意思是在 history 記錄中向前或者后退多少步秀存,類似 window.history.go(n)捶码。

this.$router.go(1) // 前進一步
this.$router.go(-1) // 后退一步
this.$router.go(-100) // 如果記錄不夠用,默默失敗不阻塞

?

# 常用路由對象屬性

  • 當前路由 - $route.path
    Path: { path: '/user/:id' }
    Url: user/12345
    說明:總是解析為絕對路徑或链,無視是否為路徑參數(shù)惫恼。返回對象
const path = this.$route.path
// user/12345
  • url參數(shù) - $route.query
    Path: { path: '/user' }
    Url: /user?id=e9e0jh830d
const query = this.$route.query
// { id: e9e0jh830d }
  • 路由參數(shù)- $route.params
    Path: { path: '/user/:id' }
    Url: /user/12345
const para = this.$route.params
// { id: 12345 }

?

# 常用路由導航守衛(wèi)

  • 全局前置守衛(wèi) - router.beforeEach

? 這是最常使用也是最重要的守衛(wèi)之一,使用時要調用 next() 方法表示導航 resolved澳盐;因為守衛(wèi)是異步解析執(zhí)行祈纯,沒有resolve的導航守衛(wèi)處在等待中阻斷執(zhí)行進程令宿,路由也不會實現(xiàn)跳轉

next()允許接收一個參數(shù):(1)false - 中斷當前導航,(2){ path: '/route_name' } - 實現(xiàn)跳轉

router.beforeEach((to, from, next) => {
  // `to` 和 `from`都是路由對象
})
  • 全局后置鉤子 - router.afterEach

? 與前置導航守衛(wèi)不同的是腕窥,afterEach并不是“守衛(wèi)”粒没,而是“鉤子”,它不會阻塞線程簇爆,也不需要調用next()來表達resolve狀態(tài)癞松。可以認為是線程流水線里的一個作業(yè)而已

router.afterEach((to, from) => { ... })
  • 私有路由守衛(wèi) - router.beforeEnter

? 與beforeEachafterEach不同的是入蛆,beforeEnter是針對單個路由進行配置的導航守衛(wèi)响蓉,作用于所在路由內而不影響其他路由路徑。該守衛(wèi)也需要調用next()來表示守衛(wèi)結束狀態(tài)

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})

?
? 另外還有 組件內的守衛(wèi) 支持在組件內直接定義導航守衛(wèi)哨毁。需注意的是枫甲,其中的beforeRouterEnter守衛(wèi)不能訪問this對象,理由是守衛(wèi)在導航確認前即將登場的新組件并未被創(chuàng)建扼褪,需要通過 next(vm) 中的 vm 參數(shù)來調用組件實例

export default {
  data () {
    ...
  },
  beforeRouteEnter (to, from, next) {
    getPost(to.params.id, (err, post) => {
      next(vm => vm.setData(err, post))
    })
  },
  methods: { ... }
}
  • 完整的導航解析流程
  1. 導航被觸發(fā)想幻。
  2. 在失活的組件里調用離開守衛(wèi)。
  3. 調用全局的 beforeEach 守衛(wèi)迎捺。
  4. 在重用的組件里調用 beforeRouteUpdate 守衛(wèi) (2.2+)举畸。
  5. 在路由配置里調用 beforeEnter。
  6. 解析異步路由組件凳枝。
  7. 在被激活的組件里調用 beforeRouteEnter抄沮。
  8. 調用全局的 beforeResolve 守衛(wèi) (2.5+)。
  9. 導航被確認岖瑰。
  10. 調用全局的 afterEach 鉤子叛买。
  11. 觸發(fā) DOM 更新。
  12. 用創(chuàng)建好的實例調用 beforeRouteEnter 守衛(wèi)中傳給 next 的回調函數(shù)
    ?

# 路由過渡動效

? <router-view> 是基本的動態(tài)組件蹋订,可以用 <transition> 組件增加過渡效果

<transition>
  <router-view></router-view>
</transition>

? 以上方式會給所有的路由增加相同的簡單的切換過渡效果率挣,如果想個性化給每個路由增加,則需要在組件內使用 <transition> 并設置不同的 name實現(xiàn)頁面級的過渡效果

<transition name="fade" mode="out-in">
  <router-view/> <!-- 也可以直接寫在某個具體的組件內<template>...</template> -->
</transition>
.fade-enter {
  opacity:0;
}
.fade-leave{
  opacity:1;
}
.fade-enter-active{
  transition:opacity .5s;
}
.fade-leave-active{
  opacity:0;
  transition:opacity .5s;
}

?

# 路由切換滾動

? 當切換到新路由時露戒,想要頁面滾到頂部椒功,或者是保持原先的滾動位置,就像重新加載頁面那樣智什。 vue-router 能做到动漾,而且更好,它讓你可以自定義路由切換時頁面如何滾動荠锭。該功能需要瀏覽器支持history.pushBehaviorAPI

const router = new VueRouter({
  routes: [...],
  scrollBehavior (to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition // 滾動到上一個位置
    } else {
      return { x: 0, y: 0 } // 滾動到頂部
    }
  }
})

? 或者可以模擬滾動到某個錨點

scrollBehavior (to, from, savedPosition) {
  if (to.hash) {
    return {
      selector: to.hash
    }
  }
}

? 甚至可以配合路由元信息meta實現(xiàn)更細顆粒度控制滾動旱眯,和利用返回一個Promise來得出預期的位置滾動。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市删豺,隨后出現(xiàn)的幾起案子共虑,更是在濱河造成了極大的恐慌,老刑警劉巖呀页,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件妈拌,死亡現(xiàn)場離奇詭異,居然都是意外死亡赔桌,警方通過查閱死者的電腦和手機供炎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來疾党,“玉大人音诫,你說我怎么就攤上這事⊙┪唬” “怎么了竭钝?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長雹洗。 經(jīng)常有香客問我香罐,道長,這世上最難降的妖魔是什么时肿? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任庇茫,我火速辦了婚禮,結果婚禮上螃成,老公的妹妹穿的比我還像新娘旦签。我一直安慰自己,他們只是感情好寸宏,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布宁炫。 她就那樣靜靜地躺著,像睡著了一般氮凝。 火紅的嫁衣襯著肌膚如雪羔巢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天罩阵,我揣著相機與錄音竿秆,去河邊找鬼。 笑死稿壁,一個胖子當著我的面吹牛幽钢,可吹牛的內容都是我干的。 我是一名探鬼主播常摧,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼搅吁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了落午?” 一聲冷哼從身側響起谎懦,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎溃斋,沒想到半個月后界拦,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡梗劫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年享甸,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片梳侨。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡蛉威,死狀恐怖,靈堂內的尸體忽然破棺而出走哺,到底是詐尸還是另有隱情蚯嫌,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布丙躏,位于F島的核電站择示,受9級特大地震影響,放射性物質發(fā)生泄漏晒旅。R本人自食惡果不足惜栅盲,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望废恋。 院中可真熱鬧谈秫,春花似錦、人聲如沸拴签。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蚓哩。三九已至构灸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間岸梨,已是汗流浹背喜颁。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留曹阔,地道東北人半开。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像赃份,于是被迫代替她去往敵國和親寂拆。 傳聞我的和親對象是個殘疾皇子奢米,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內容