溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

vue-router的工作原理

發(fā)布時間:2021-09-02 14:51:09 來源:億速云 閱讀:124 作者:chen 欄目:web開發(fā)

本篇內(nèi)容主要講解“vue-router的工作原理”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學(xué)習(xí)“vue-router的工作原理”吧!

單頁面應(yīng)用的工作原理

我理解的單頁面工作原理是通過瀏覽器URL的#后面的hash變化就會引起頁面變化的特性來把頁面分成不同的小模塊,然后通過修改hash來讓頁面展示我們想讓看到的內(nèi)容。

那么為什么hash的不同,為什么會影響頁面的展示呢?瀏覽器在這里面做了什么內(nèi)容。以前#后面的內(nèi)容一般會做錨點,但是會定位到一個頁面的某個位置,這個是怎么做到的呢,和我們現(xiàn)在的路由有什么不同。(我能想到一個路由的展示就會把其他路由隱藏,是這樣的嗎)后面會看一看寫一下這個疑惑,現(xiàn)在最重要的是先把基本概念弄熟。

正文

當(dāng)你要把 vue-router 添加進來,我們需要做的是,將組件(components)映射到路由(routes),然后告訴 vue-router 在哪里渲染它們

起步

//*** router-link 告訴瀏覽器去哪個路由
//*** router-view 告訴路由在哪里展示內(nèi)容
<div id="app">
 <h2>Hello App!</h2>
 <p>
 <!-- 使用 router-link 組件來導(dǎo)航. -->
 <!-- 通過傳入 `to` 屬性指定鏈接. -->
 <!-- <router-link> 默認會被渲染成一個 `<a>` 標(biāo)簽 -->
 <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. 定義路由
// 每個路由應(yīng)該映射一個組件。 其中"component" 可以是
// 通過 Vue.extend() 創(chuàng)建的組件構(gòu)造器,
// 或者,只是一個組件配置對象。
// 我們晚點再討論嵌套路由。
const routes = [
 { path: '/foo', component: Foo },
 { path: '/bar', component: Bar }
]
// 3. 創(chuàng)建 router 實例,然后傳 `routes` 配置
// 你還可以傳別的配置參數(shù), 不過先這么簡單著吧。
const router = new VueRouter({
 routes // (縮寫)相當(dāng)于 routes: routes
})
// 4. 創(chuàng)建和掛載根實例。
// 記得要通過 router 配置參數(shù)注入路由,
// 從而讓整個應(yīng)用都有路由功能
const app = new Vue({
 router
}).$mount('#app')
// 現(xiàn)在,應(yīng)用已經(jīng)啟動了!

動態(tài)路由匹配

相當(dāng)于同一個組件,因為參數(shù)不同展示不同的組件內(nèi)容,其實就是在 vue-router 的路由路徑中使用『動態(tài)路徑參數(shù)』

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

那么我們進入uesr/001 user/002 其實是進入的同一個路由,可以根據(jù)參數(shù)的不同在內(nèi)容頁展示不同的內(nèi)容。一般適用場景:列表,權(quán)限控制

定義的時候用: 表示是動態(tài)路由

使用 {{ $route.params.id }} 來拿到本路由里面參數(shù)的內(nèi)容

當(dāng)使用路由參數(shù)時,例如從 /user/foo 導(dǎo)航到 /user/bar,原來的組件實例會被復(fù)用。因為兩個路由都渲染同個組件,比起銷毀再創(chuàng)建,復(fù)用則顯得更加高效。不過,這也意味著組件的生命周期鉤子不會再被調(diào)用。

復(fù)用組件時,想對路由參數(shù)的變化作出響應(yīng)的話,你可以簡單地 watch(監(jiān)測變化) $route 對象

const User = {
 template: '...',
 watch: {
 '$route' (to, from) {
  // 對路由變化作出響應(yīng)...
 }
 }
}

有時候,同一個路徑可以匹配多個路由,此時,匹配的優(yōu)先級就按照路由的定義順序:誰先定義的,誰的優(yōu)先級就最高。

嵌套路由

在路由里面嵌套一個路由

//路由里面也會出現(xiàn) <router-view> 這是嵌套路由展示內(nèi)容的地方
const User = {
 template: `
 <div class="user">
  <h3>User {{ $route.params.id }}</h3>
  <router-view></router-view>
 </div>
 `
}
//定義路由的時候在 加children 子路由屬性
const router = new VueRouter({
 routes: [
 { path: '/user/:id', component: User,
  children: [
  {
   // 當(dāng) /user/:id/profile 匹配成功,
   // UserProfile 會被渲染在 User 的 <router-view> 中
   path: 'profile',
   component: UserProfile
  },
  {
   // 當(dāng) /user/:id/posts 匹配成功
   // UserPosts 會被渲染在 User 的 <router-view> 中
   path: 'posts',
   component: UserPosts
  }
  ]
 }
 ]
})

設(shè)置空路由,在沒有指定路由的時候就會展示空路由內(nèi)容

const router = new VueRouter({
 routes: [
 {
  path: '/user/:id', component: User,
  children: [
  // 當(dāng) /user/:id 匹配成功,
  // UserHome 會被渲染在 User 的 <router-view> 中
  { path: '', component: UserHome },
  ]
 }
 ]
})

編程式導(dǎo)航

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

可以想象編程式 push 可以理解為向瀏覽器歷史里面push一個新的hash,導(dǎo)致路由發(fā)生變化

router.replace()  修改路由但是不存在歷史里面
router.go(n)      有點像JS的window.history.go(n)

命名路由 就是給每一個路由定義一個名字。

命名視圖

有時候想同時(同級)展示多個視圖,而不是嵌套展示,例如創(chuàng)建一個布局,有 sidebar(側(cè)導(dǎo)航) 和 main(主內(nèi)容) 兩個視圖,這個時候命名視圖就派上用場了。你可以在界面中擁有多個單獨命名的視圖,而不是只有一個單獨的出口。如果 router-view 沒有設(shè)置名字,那么默認為 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
   }
  }
 ]
})

重定向和別名

重定向也是通過 routes 配置來完成,下面例子是從 /a 重定向到 /b:

const router = new VueRouter({
 routes: [
  { path: '/a', redirect: '/b' }
 ]
})

一般首頁的時候可以重定向到其他的地方

重定向的目標(biāo)也可以是一個命名的路由:

const router = new VueRouter({
 routes: [
  { path: '/a', redirect: { name: 'foo' }}
 ]
})

甚至是一個方法,動態(tài)返回重定向目標(biāo):

const router = new VueRouter({
 routes: [
  { path: '/a', redirect: to => {
   // 方法接收 目標(biāo)路由 作為參數(shù)
   // return 重定向的 字符串路徑/路徑對象
  }}
 ]
})

『重定向』的意思是,當(dāng)用戶訪問 /a時,URL 將會被替換成 /b,然后匹配路由為 /b,那么『別名』又是什么呢?

/a 的別名是 /b,意味著,當(dāng)用戶訪問 /b 時,URL 會保持為 /b,但是路由匹配則為 /a,就像用戶訪問 /a 一樣。

上面對應(yīng)的路由配置為:

const router = new VueRouter({
 routes: [
  { path: '/a', component: A, alias: '/b' }
 ]
})

『別名』的功能讓你可以自由地將 UI 結(jié)構(gòu)映射到任意的 URL,而不是受限于配置的嵌套路由結(jié)構(gòu)。

HTML5 History 模式

ue-router 默認 hash 模式 —— 使用 URL 的 hash 來模擬一個完整的 URL,于是當(dāng) URL 改變時,頁面不會重新加載。

如果不想要很丑的 hash,我們可以用路由的 history 模式,這種模式充分利用 history.pushState API 來完成 URL 跳轉(zhuǎn)而無須重新加載頁面。

const router = new VueRouter({
 mode: 'history',
 routes: [...]
})

當(dāng)你使用 history 模式時,URL 就像正常的 url,例如 http://yoursite.com/user/id,也好看!

不過這種模式要玩好,還需要后臺配置支持。因為我們的應(yīng)用是個單頁客戶端應(yīng)用,如果后臺沒有正確的配置,當(dāng)用戶在瀏覽器直接訪問 http://oursite.com/user/id 就會返回 404,這就不好看了。

所以呢,你要在服務(wù)端增加一個覆蓋所有情況的候選資源:如果 URL 匹配不到任何靜態(tài)資源,則應(yīng)該返回同一個 index.html 頁面,這個頁面就是你 app 依賴的頁面。

給個警告,因為這么做以后,你的服務(wù)器就不再返回 404 錯誤頁面,因為對于所有路徑都會返回 index.html 文件。為了避免這種情況,你應(yīng)該在 Vue 應(yīng)用里面覆蓋所有的路由情況,然后在給出一個 404 頁面。

const router = new VueRouter({
 mode: 'history',
 routes: [
  { path: '*', component: NotFoundComponent }
 ]
})

或者,如果你使用 Node.js 服務(wù)器,你可以用服務(wù)端路由匹配到來的 URL,并在沒有匹配到路由的時候返回 404,以實現(xiàn)回退。

導(dǎo)航守衛(wèi)

我的理解 就是組件或者全局級別的 組件的鉤子函數(shù)

正如其名,vue-router 提供的導(dǎo)航守衛(wèi)主要用來通過跳轉(zhuǎn)或取消的方式守衛(wèi)導(dǎo)航。有多種機會植入路由導(dǎo)航過程中:全局的, 單個路由獨享的, 或者組件級的。

記住參數(shù)或查詢的改變并不會觸發(fā)進入/離開的導(dǎo)航守衛(wèi)。你可以通過觀察 $route 對象來應(yīng)對這些變化,或使用 beforeRouteUpdate 的組件內(nèi)守衛(wèi)。

全局守衛(wèi)

const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
 // ...
})

每個守衛(wèi)方法接收三個參數(shù):

to: Route: 即將要進入的目標(biāo) 路由對象

from: Route: 當(dāng)前導(dǎo)航正要離開的路由

next: Function: 一定要調(diào)用該方法來 resolve 這個鉤子。執(zhí)行效果依賴 next 方法的調(diào)用參數(shù)。

next(): 進行管道中的下一個鉤子。如果全部鉤子執(zhí)行完了,則導(dǎo)航的狀態(tài)就是 confirmed (確認的)。

next(false): 中斷當(dāng)前的導(dǎo)航。如果瀏覽器的 URL 改變了(可能是用戶手動或者瀏覽器后退按鈕),那么 URL 地址會重置到 from 路由對應(yīng)的地址。

next(‘/') 或者 next({ path: ‘/' }): 跳轉(zhuǎn)到一個不同的地址。當(dāng)前的導(dǎo)航被中斷,然后進行一個新的導(dǎo)航。你可以向 next 傳遞任意位置對象,且允許設(shè)置諸如 replace: true、name: ‘home' 之類的選項以及任何用在 router-link 的 to prop 或 router.push 中的選項。

next(error): (2.4.0+) 如果傳入 next 的參數(shù)是一個 Error 實例,則導(dǎo)航會被終止且該錯誤會被傳遞給 router.onError() 注冊過的回調(diào)。

確保要調(diào)用 next 方法,否則鉤子就不會被 resolved。

全局后置鉤子

你也可以注冊全局后置鉤子,然而和守衛(wèi)不同的是,這些鉤子不會接受 next 函數(shù)也不會改變導(dǎo)航本身:

router.afterEach((to, from) => {
 // ...
})

路由獨享的守衛(wèi)

你可以在路由配置上直接定義 beforeEnter 守衛(wèi):

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

這些守衛(wèi)與全局前置守衛(wèi)的方法參數(shù)是一樣的。

組件內(nèi)的守衛(wèi)

最后,你可以在路由組件內(nèi)直接定義以下路由導(dǎo)航守衛(wèi):

beforeRouteEnter 
beforeRouteUpdate (2.2 新增) 
beforeRouteLeave

const Foo = {
 template: `...`,
 beforeRouteEnter (to, from, next) {
  // 在渲染該組件的對應(yīng)路由被 confirm 前調(diào)用
  // 不!能!獲取組件實例 `this`
  // 因為當(dāng)守衛(wèi)執(zhí)行前,組件實例還沒被創(chuàng)建
 },
 beforeRouteUpdate (to, from, next) {
  // 在當(dāng)前路由改變,但是該組件被復(fù)用時調(diào)用
  // 舉例來說,對于一個帶有動態(tài)參數(shù)的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉(zhuǎn)的時候,
  // 由于會渲染同樣的 Foo 組件,因此組件實例會被復(fù)用。而這個鉤子就會在這個情況下被調(diào)用。
  // 可以訪問組件實例 `this`
 },
 beforeRouteLeave (to, from, next) {
  // 導(dǎo)航離開該組件的對應(yīng)路由時調(diào)用
  // 可以訪問組件實例 `this`
 }
}

beforeRouteEnter 守衛(wèi) 不能 訪問 this,因為守衛(wèi)在導(dǎo)航確認前被調(diào)用,因此即將登場的新組件還沒被創(chuàng)建。

完整的導(dǎo)航解析流程

導(dǎo)航被觸發(fā)。
在失活的組件里調(diào)用離開守衛(wèi)。
調(diào)用全局的 beforeEach 守衛(wèi)。
在重用的組件里調(diào)用 beforeRouteUpdate 守衛(wèi) (2.2+)。
在路由配置里調(diào)用 beforeEnter。
解析異步路由組件。
在被激活的組件里調(diào)用 beforeRouteEnter。
調(diào)用全局的 beforeResolve 守衛(wèi) (2.5+)。
導(dǎo)航被確認。
調(diào)用全局的 afterEach 鉤子。
觸發(fā) DOM 更新。
用創(chuàng)建好的實例調(diào)用 beforeRouteEnter 守衛(wèi)中傳給 next 的回調(diào)函數(shù)。

路由元信息

我的理解就是 他可以把路由的父路徑都列舉出來,完成一些任務(wù),比如登錄,user 組件需要登錄,那么user下面的foo組件也需要,那么可以通過這個屬性 來檢測這個路由線上 的一些狀態(tài)。

定義路由的時候可以配置 meta 字段:

const router = new VueRouter({
 routes: [
  {
   path: '/foo',
   component: Foo,
   children: [
    {
     path: 'bar',
     component: Bar,
     // a meta field
     meta: { requiresAuth: true }
    }
   ]
  }
 ]
})

首先,我們稱呼 routes 配置中的每個路由對象為 路由記錄。路由記錄可以是嵌套的,因此,當(dāng)一個路由匹配成功后,他可能匹配多個路由記錄

例如,根據(jù)上面的路由配置,/foo/bar 這個 URL 將會匹配父路由記錄以及子路由記錄。

一個路由匹配到的所有路由記錄會暴露為 $route 對象(還有在導(dǎo)航守衛(wèi)中的路由對象)的 $route.matched 數(shù)組。因此,我們需要遍歷 $route.matched 來檢查路由記錄中的 meta 字段。

下面例子展示在全局導(dǎo)航守衛(wèi)中檢查元字段:

router.beforeEach((to, from, next) => {
 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({
    path: '/login',
    query: { redirect: to.fullPath }
   })
  } else {
   next()
  }
 } else {
  next() // 確保一定要調(diào)用 next()
 }
})

數(shù)據(jù)獲取

我的理解就是在哪里獲取數(shù)據(jù),可以再組件里面,也可以在組件的守衛(wèi)里面,也就是組件的生命周期里面。

有時候,進入某個路由后,需要從服務(wù)器獲取數(shù)據(jù)。例如,在渲染用戶信息時,你需要從服務(wù)器獲取用戶的數(shù)據(jù)。我們可以通過兩種方式來實現(xiàn):

導(dǎo)航完成之后獲?。合韧瓿蓪?dǎo)航,然后在接下來的組件生命周期鉤子中獲取數(shù)據(jù)。在數(shù)據(jù)獲取期間顯示『加載中』之類的指示。

導(dǎo)航完成之前獲?。簩?dǎo)航完成前,在路由進入的守衛(wèi)中獲取數(shù)據(jù),在數(shù)據(jù)獲取成功后執(zhí)行導(dǎo)航。

從技術(shù)角度講,兩種方式都不錯 —— 就看你想要的用戶體驗是哪種。

導(dǎo)航完成后獲取數(shù)據(jù)

當(dāng)你使用這種方式時,我們會馬上導(dǎo)航和渲染組件,然后在組件的 created 鉤子中獲取數(shù)據(jù)。這讓我們有機會在數(shù)據(jù)獲取期間展示一個 loading 狀態(tài),還可以在不同視圖間展示不同的 loading 狀態(tài)。

假設(shè)我們有一個 Post 組件,需要基于 $route.params.id 獲取文章數(shù)據(jù):

<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">
   <h3>{{ post.title }}</h3>
   <p>{{ post.body }}</p>
  </div>
 </div>
</template>
export default {
 data () {
  return {
   loading: false,
   post: null,
   error: null
  }
 },
 created () {
  // 組件創(chuàng)建完后獲取數(shù)據(jù),
  // 此時 data 已經(jīng)被 observed 了
  this.fetchData()
 },
 watch: {
  // 如果路由有變化,會再次執(zhí)行該方法
  '$route': 'fetchData'
 },
 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 {
     this.post = post
    }
   })
  }
 }
}

在導(dǎo)航完成前獲取數(shù)據(jù)

通過這種方式,我們在導(dǎo)航轉(zhuǎn)入新的路由前獲取數(shù)據(jù)。我們可以在接下來的組件的 beforeRouteEnter 守衛(wèi)中獲取數(shù)據(jù),當(dāng)數(shù)據(jù)獲取成功后只調(diào)用 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))
  })
 },
 // 路由改變前,組件就已經(jīng)渲染完了
 // 邏輯稍稍不同
 beforeRouteUpdate (to, from, next) {
  this.post = null
  getPost(to.params.id, (err, post) => {
   this.setData(err, post)
   next()
  })
 },
 methods: {
  setData (err, post) {
   if (err) {
    this.error = err.toString()
   } else {
    this.post = post
   }
  }
 }
}

在為后面的視圖獲取數(shù)據(jù)時,用戶會停留在當(dāng)前的界面,因此建議在數(shù)據(jù)獲取期間,顯示一些進度條或者別的指示。如果數(shù)據(jù)獲取失敗,同樣有必要展示一些全局的錯誤提醒。

到此,相信大家對“vue-router的工作原理”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

vue
AI