vueRouter路由詳解:從入門到精通

Vue-Router

什麼是路由?

路由是根據不同的url地址展現不同的內容或頁面。早期的路由都是後端直接根據url來重載頁面實現的,即後端控制路由。後來頁面越來越複雜,服務器壓力越來越大,隨着ajax(異步刷新技術)的出現,頁面的實現非重載就能刷新數據,讓前端也可以控制url自行管理,前端路由由此而生。

什麼時候使用前端路由?

前端路由更多用在單頁應用上,也就是SPA(Single Page Web Application),在單頁面應用中,大部分頁面保持不變,只改變部分內容的使用。

路由基本使用的步驟

安裝路由

npm install vue-router

使用路由

  1. 引入路由
import VueRouter from 'vue-router';
  1. 使用路由
Vue.use(VueRouter);
  1. 定義路由組件
// 可以從其他文件 import 進來
const Home = { template: '<div>Home page</div>' }
const About = { template: '<div>about page</div>' }
  1. 定義路由
// 每個路由應該映射一個組件
const routes = [
  { path: '/home', component: Home },
  { path: '/about', component: About }
]
  1. 創建 router 實例,然後傳 routes 配置
const router = new VueRouter({
  routes 
})
  1. 創建和掛載根實例
const app = new Vue({
  router
}).$mount('#app')
  1. 在html中使用路由 <router-link></router-link><router-view></router-view>
<div id="app">
  <h1>Hello App!</h1>
  <ul>
    <!-- 使用 router-link 組件來導航 -->
    <router-link to="/home">首頁</router-link>
    <router-link to="/about" tag="li">關於</router-link>
  </ul>
    <!-- 路由出口 -->
  <router-view></router-view>
</div>

注意

  • router-link:通過傳入 to 屬性指定鏈接,默認會被渲染成一個 <a> 標籤,可以通過tag屬性指定標籤類型

  • router-view:路由出口,路由匹配到的組件將渲染在這裏

路由樣式

如果我們想要爲當前選中的路由設置樣式的話,就可以通過下面兩個類名:

  • router-link-exact-active:當前展示路徑完全匹配組件to屬性的值
  • router-link-active : 當前展示路徑包含to屬性的值

我們可以通過配置更改選中路由的 class 名

new VueRouter({
  linkActiveClass: 'link-active',
  linkExactActiveClass: 'link-exact-active',
})

HTML5 History 模式

hash模式:vue-router 默認 hash 模式,使用 URL 的 hash 來模擬一個完整的 URL,於是當 URL 改變時,頁面不會重新加載。

history 模式:這種模式充分利用 history.pushState API 來完成 URL 跳轉而無須重新加載頁面。

在路由配置中設置:

new VueRouter({
  mode: 'history',
})

我們在項目中通常使用的是 history 模式,因爲 hash 模式太醜啦。使用這種模式,我們通常會在服務端增加一個覆蓋所有情況的候選資源:如果 URL 匹配不到任何靜態資源,則返回一個頁面,或跳轉到首頁。

命名路由

可以通過一個名稱標識一個路由,這樣在某些時候會顯得更方便一些,特別是在鏈接一個路由,或者是執行一些跳轉時,可以在創建Router實例時,在routes配置中給某個路由設置名稱:

routes = [
  {
    path: '/home',
    name: 'Home',
    component: Home,
  }
];

要鏈接到一個命名路由,可以給 router-link 的 to 屬性傳一個對象:

<router-link :to="{ name: 'Home' }">首頁</router-link>

嵌套路由

一個被 router-view 渲染的組件想要包含自己的嵌套 router-view 時,可以使用嵌套路由,通常的說法叫子路由,如:

{
  path: '/about',
  component: () => import('./views/About'),
  children: [
    {
      path: '/about/us',
      component: () => import('./views/Us'),
    },
    {
      path: '/about/company',
      component: () => import('./views/Company'),
    }
  ],
}

經過這樣的設置,在 About 組件中就可以使用 router-view 了。子路由的 path 可以簡寫

{
  path: '/about',
  component: () => import('./views/About'),
  children: [
    {
      path: 'us',
      component: () => import('./views/Us'),
    }
  ],
}

這樣會自動將父路由的路徑,拼接在子路由前,最終結果爲:/about/us

當訪問 /about下的其他路徑時,並不會渲染出來任何東西,如果想要渲染點什麼,可以提供一個空路由

{
  path: '/about',
  children: [
    {
      path: '',
      component: () => import('./views/SomeComp'),
    },
  ],
}

重定向

重定向也是通過 routes 配置來完成,通過 redirect 屬性,下面例子是從根路徑/ 重定向到 /home

const router = new VueRouter({
  routes: [
  	{
    	path: '/',
    	redirect: "/home"
  	},
  	{
    	path: '/home',
    	name: 'Home',
    	component: Home,
  	},
  ]
})

重定向的目標也可以是一個命名的路由:

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

甚至是一個方法,動態返回重定向目標:

const router = new VueRouter({
  routes: [
    { path: '/', redirect: to => {
        return {
            path:"/home"
        }
    }}
  ]
})

注意:方法接收目標路由作爲參數,return 重定向的字符串路徑/路徑對象

別名

“重定向” 的意思是,當用戶訪問根/ 時,URL 將會被替換成 /home,匹配路由爲 /home

那麼“別名”又是什麼呢?

/home 的別名是 /,意味着,當用戶訪問 / 時,URL 會保持爲 /,但是路由匹配則爲 /home,就像用戶訪問 /home 一樣。

上面對應的路由配置爲:

const router = new VueRouter({
  routes: [
    { path: '/home', component: Home, alias: '/' }
  ]
})

編程式的導航

通過在 Vue 根實例的 router 配置傳入 router 實例,$router$route 兩個屬性會被注入到每個子組件。

$router

路由實例對象。除了使用 <router-link> 創建 a 標籤來定義導航鏈接,我們還可以藉助 router 的實例
方法,通過編寫代碼來實現。

  • $router.push

想要導航到不同的 URL,則使用 router.push 方法。這個方法會向 history 棧添加一個新的記錄,所以,當用戶點擊瀏覽器後退按鈕時,則回到之前的 URL

當你點擊 <router-link> 時,這個方法會在內部調用,所以說,點擊 <router-link :to="..."> 等同於調用 $router.push(...)

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

該方法的參數可以是一個字符串路徑,或者一個描述地址的對象。例如:

// 字符串
this.$router.push('/home')

// 對象
this.$router.push({ path: '/home' })

// 命名的路由
this.$router.push({ name: 'home' })
  • $router.replace

router.push 很像,唯一的不同就是,它不會向 history 添加新記錄,而是替換掉當前的 history 記錄。

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

這個方法的參數是一個整數,意思是在 history 記錄中向前或者後退多少步,類似 window.history.go(n)

// 在瀏覽器記錄中前進一步,等同於 history.forward()
this.$router.go(1)

// 後退一步記錄,等同於 history.back()
this.$router.go(-1)

// 前進 3 步記錄
this.$router.go(3)

// 如果 history 記錄不夠用,會失敗
this.$router.go(-100)
this.$router.go(100)

$route

只讀,路由信息對象。當我們想要獲取當前路由的信息,就可以使用這個實例對象

  • $route.path:字符串,對應當前路由的路徑,總是解析爲絕對路徑,如 "/home/a"

  • $route.params:一個 key/value 對象,包含了動態片段和全匹配片段,如果沒有路由參數,就是一個空對象。

  • $route.query:一個 key/value 對象,表示 URL 查詢參數。例如,對於路徑 /detail?id=1,則有 $route.query.id== 1,如果沒有查詢參數,則是個空對象。

  • $route.hash:路由的 hash 值 (帶 #) ,如果沒有 hash 值,則爲空字符串。

  • $route.fullPath:完成解析後的 URL,包含查詢參數和 hash 的完整路徑。

  • $route.matched:一個數組,包含當前路由的所有嵌套路徑片段的路由記錄 。路由記錄就是 routes 配置數組中的對象副本 (還有在 children 數組)。將會是一個包含從上到下的所有對象 (副本)。

  • $route.name:當前路由的名稱,如果沒有則是空字符串

  • $route.redirectedFrom:如果存在重定向,即爲重定向來源的路由的名字。

舉個例子

在這裏插入圖片描述

動態路由匹配

當我們需要把某種模式匹配到的所有路由,全都映射到同個組件。就像csdn上博客文章https://blog.csdn.net/Newbie___/article/details/105480827,我們來模仿一下,我們有一個 Detail 組件,對於所有 ID 各不相同的文章,都要使用這個組件來渲染。那麼,我們可以在 vue-router 的路由路徑中使用 “ 動態路徑參數 ” 來達到這個效果:

const Detail = {
  template: '<div>Detail</div>'
}

const router = new VueRouter({
  routes: [
    // 動態路徑參數 以冒號開頭
    { path: '/details/:id', name:"Details", component: Detail }
  ]
})

經過這樣的設置,像 /details/123/details/456 都將映射到相同的路由。

一個“路徑參數” 使用冒號 : 標記。當匹配到一個路由時,參數值會被設置到 this.$route.params,可以在每個組件內使用。

使用方式一:

this.$router.push({ path: `/details/${this.product.id}` });

使用方式二:當然我們的id應該是動態的,但我這裏就不拼接了,使用一個靜態id

<router-link :to="{name:'Details',params:{id:12345}}">click me</router-link>

使用方式三:

<router-link :to="{path:'/details/12345'}">click me</router-link>

命名視圖

想同時展示多個視圖時,並且每個視圖展示不同的組件時,可以使用命名視圖。

可以在界面中擁有多個單獨命名的視圖,而不是隻有一個單獨的出口。如果 router-view 沒有設置名字,那麼默認爲 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 配置:

const router = new VueRouter({
  routes: [
    {
      path: '/',
      components: {
        default: Foo,
        a: Bar,
        b: Baz
      }
    }
  ]
})

路由組件傳參

在組件中使用 $route 會使之與其對應路由形成高度耦合,從而使組件只能在某些特定的 URL 上使用,限制了其靈活性。我們可以使用使用 props 將組件和路由解耦。

布爾模式

如果 props 被設置爲 trueroute.params 將會被設置爲組件屬性。

const routes = [
//...
  {
    path: '/details/:id',
    name: 'Details',
    props:true,
    component: () => import('@views/ProductDetails.vue')
  }
]

在頁面中,我們就可以接受屬性,如 id:

props:{
    id:{
        type:[String,Number]
    }
},

對象模式

如果 props 是一個對象,它會被按原樣設置爲組件屬性。當 props 是靜態的時候有用。

const router = new VueRouter({
  routes: [
    {
        path: '/details/:id',
        name: 'Details',
        props: {
            name: "detail-page"
        },
        component: () => import('@views/ProductDetails.vue')
  	}
  ]
})

函數模式

你可以創建一個函數返回 props。函數的第一個參數是 route (即$route)。

const router = new VueRouter({
  routes: [
    {
    path: '/details/:id',
    name: 'Details',
    props:(route)=>{
      return {
        id:route.params.id,
        name:route.name
      }
    },
    component: () => import('@views/ProductDetails.vue')
  }
  ]
})
props:{
    id:{
        type:[String,Number]
    },
    name:{
        type:String
    }
}

導航守衛

什麼是導航守衛呢?導航說的是路由正在發生變化,導航守衛就是通過跳轉或取消的方式來守衛導航。導航守衛被分成三種:全局的單個路由獨享的組件內的

在導航守衛中一般都會接收這三個參數:

  • to:目標路由對象
  • from:即將要離開的路由對象
  • next:三個參數中最重要的參數。
    • 必須調用next(),才能繼續往下執行一個鉤子,否則路由跳轉會停止
    • 若要中斷當前的導航,可以調用next(false)
    • 可以使用 next('/home') 的方式跳轉到一個不同的地址。終止當前導航,進入一個新的導航。next參數值和$routet.push一致。
    • next(error)。2.4+,如果傳入的參數是一個 Error 實例,則導航會被終止,且該錯誤會被傳遞給router.onError() 註冊過的回調。

舉個例子:to 和 from 對象的內容(與上面$route內容相同)
在這裏插入圖片描述

全局守衛

全局守衛是指路由實例上直接操作的鉤子函數,觸發路由就會觸發這些鉤子函數。

  • 全局前置守衛 beforeEach

在路由跳轉前觸發,一般被用於登錄驗證。

const router = new VueRouter({ ... })

router.beforeEach((to, from, next) => {
  // ...
})
  • 全局解析守衛 beforeResolve

和 boforeEach 類似,路由跳轉前觸發。和 beforeEach 的區別:在導航被確認之前,同時在所有組件內守衛和異步路由組件被解析之後,解析守衛就被調用。

const router = new VueRouter({ ... })

router.beforeResolve((to, from, next) => {
  // ...
})
  • 全局後置鉤子 afterEach

和 beforeEach 相反,路由跳轉完成後觸發。

const router = new VueRouter({ ... })

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

路由獨享守衛

是指在單個路由配置的時候也可以設置的鉤子函數。

  • beforeEnter

和beforeEach完全相同,如果都設置則在beforeEach之後緊隨執行。進入該路由時才起作用

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

組件內守衛

組件內守衛是指在組件內執行的鉤子函數,類似於組件內的生命週期,相當於爲配置路由的組件添加的生命週期鉤子函數。

  • beforeRouteEnter:路由進入之前調用。

在該守衛內訪問不到組件的實例,this 值爲 undefined。在這個鉤子函數中,可以通過傳一個回調給 next 來訪問組件實例。在導航被確認的時候執行回調,並且把組件實例作爲回調方法的參數,可以在這個守衛中請求服務端獲取數據,當成功獲取並能進入路由時,調用 next 並在回調中通過 vm 訪問組件實例進行賦值等操作,(next中函數的調用在 mounted 之後:爲了確保能對組件實例的完整訪問)。

beforeRouteEnter (to, from, next) {
    // 在渲染該組件的對應路由被 confirm 前調用
    // 不!能!獲取組件實例 `this`
    // 因爲當守衛執行前,組件實例還沒被創建
    next( vm => {
    // 通過 `vm` 訪問組件實例
    })
},
  • beforeRouteUpdate:在當前路由改變時,並且該組件被複用時調用,可以通過this訪問實例。

何時組件會被複用?只有當動態路由間互相跳轉,路由query變更時會被複用

beforeRouteUpdate (to, from, next) {
  // 在當前路由改變,但是該組件被複用時調用
  // 舉例來說,對於一個帶有動態參數的路徑 /details/:id,在 /details/1 和 /details/2 之間跳轉的時候,
  // 由於會渲染同樣的 Foo 組件,因此組件實例會被複用。而這個鉤子就會在這個情況下被調用。
  // 可以訪問組件實例 `this`
},
  • beforeRouteLeave:導航離開該組件的對應路由時調用,可以訪問組件實例this。
beforeRouteLeave (to, from, next) {
  // 導航離開該組件的對應路由時調用
  // 可以訪問組件實例 `this`
}
// 全局守衛
router.beforeEach((to,from,next)=>{
  console.log("beforeEach守衛")
  console.log(to,from)
  next()
})
router.beforeResolve((to,from,next)=>{
  console.log("beforeResolve守衛")
  console.log(to,from)
  next()
})
router.afterEach((to,from)=>{
  console.log("afterEach")
  console.log(to,from)
})

beforeRouteEnter (to, from, next) {
        // ...
        console.log(to,from)
        console.log("beforeRouteEnter")
        next(vm=>{
            console.log(vm)
        })
    },
    beforeRouteUpdate(to,from,next){
        // ...
        console.log(to,from)
        console.log("beforeRouteUpdate")
        console.log(this)
        next()
    }
    beforeRouteLeave (to, from, next) {
     // ...
     console.log(to,from)
     console.log("beforeRouteLeave")
     next()
   }

完整的導航解析流程

  1. 導航被觸發。
  2. 在失活的組件裏調用離開守衛 beforeRouteLeave
  3. 調用全局的 beforeEach 守衛。
  4. 在重用的組件裏調用 beforeRouteUpdate 守衛 (2.2+)。
  5. 在路由配置裏調用 beforeEnter
  6. 解析異步路由組件。
  7. 在被激活的組件裏調用 beforeRouteEnter
  8. 調用全局的 beforeResolve 守衛 (2.5+)。
  9. 導航被確認。
  10. 調用全局的 afterEach 鉤子。
  11. 觸發 DOM 更新。
  12. 用創建好的實例調用 beforeRouteEnter 守衛中傳給 next 的回調函數。

我舉個例子,我在/home跳轉到/details/12345,我們來看看依次發生了什麼

想要觸發組件內守衛 beforeRouteLeave ,需要在離開的路由地址的組件中設置

//Home.vue
beforeRouteLeave (to, from, next) {
    // ...
    console.log("beforeRouteLeave守衛觸發")
    next()
},

在目標路由的組件內配置 組件內守衛 beforeRouteEnterbeforeRouteUpdate

//Details.vue
beforeRouteEnter (to, from, next) {
    // ...
    console.log("beforeRouteEnter守衛觸發")
    next(vm=>{
        console.log(vm.name) //在該組件內data中有一個變量 name:"young monk"
    })
},

beforeRouteUpdate(to, from, next){
	// ...
    console.log("beforeRouteUpdate守衛觸發")
    next()
}

在全局路由中配置全局守衛:

//index.js(路由配置的js)
const router = new VueRouter({
  	//...
})

router.beforeEach((to,from,next)=>{
  // ...
  console.log("全局beforeEach觸發")
  next()
})
router.beforeResolve((to,from,next)=>{
   // ...
  console.log("全局beforeResolve觸發")
  next()
})
router.afterEach((to,from)=>{
   // ...
  console.log("全局afterEach觸發")
})

最後依次輸出的結果是:

beforeRouteLeave守衛觸發
全局beforeEach觸發
路由獨享守衛beforeEnter觸發
beforeRouteEnter守衛觸發
全局beforeResolve觸發
全局afterEache觸發
young monk

按照上面的完整的導航解析流程,這個結果就並不意外了,唯獨組件內守衛 beforeRouteUpdate 沒有觸發,是因爲組件沒用被複用。

路由元信息

定義路由的時候可以配置 meta 字段,用於自定義一些信息。

const router = new VueRouter({
  routes: [
    {
      path: '/home',
      component: Home,
      meta: { isLogin: true }
    }
  ]
})

過渡動效

<router-view> 是基本的動態組件,所以我們可以用 <transition> 組件給它添加一些過渡效果。

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

滾動行爲

使用前端路由,當切換到新路由時,想要頁面滾到頂部,或者是保持原先的滾動位置,就像重新加載頁面那樣。vue-router 可以自定義路由切換時頁面如何滾動。

注意: 這個功能只在支持 history.pushState 的瀏覽器中可用。

當創建一個 Router 實例,你可以提供一個 scrollBehavior 方法:

const router = new VueRouter({
  routes: [...],
  scrollBehavior (to, from, savedPosition) {
    // return 期望滾動到哪個的位置
  }
})

參數

  • to:目標路由對象
  • from:原路由對象
  • savedPosition :存放上一次的位置,一個對象像這樣,{ x: 0,y: 0 }。當且僅當 popstate 導航 (通過瀏覽器的 前進/後退 按鈕觸發) 時纔可用。

返回值

  • { x: number, y: number }
  • { selector: string, offset? : { x: number, y: number }}(offset 只在 2.6.0+ 支持)
scrollBehavior (to, from, savedPosition) {
  return { x: 0, y: 0 } //回到頂部
}
scrollBehavior (to, from, savedPosition) {
  if (to.hash) {
    return {
      selector: to.hash // selector 的 值爲 hash值
    }
  }
}

位置,就像重新加載頁面那樣。vue-router 可以自定義路由切換時頁面如何滾動。

注意: 這個功能只在支持 history.pushState 的瀏覽器中可用。

當創建一個 Router 實例,你可以提供一個 scrollBehavior 方法:

const router = new VueRouter({
  routes: [...],
  scrollBehavior (to, from, savedPosition) {
    // return 期望滾動到哪個的位置
  }
})

參數

  • to:目標路由對象
  • from:原路由對象
  • savedPosition :存放上一次的位置,一個對象像這樣,{ x: 0,y: 0 }。當且僅當 popstate 導航 (通過瀏覽器的 前進/後退 按鈕觸發) 時纔可用。

返回值

  • { x: number, y: number }
  • { selector: string, offset? : { x: number, y: number }}(offset 只在 2.6.0+ 支持)
scrollBehavior (to, from, savedPosition) {
  return { x: 0, y: 0 } //回到頂部
}
scrollBehavior (to, from, savedPosition) {
  if (to.hash) {
    return {
      selector: to.hash // selector 的 值爲 hash值
    }
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章