vue-router詳細介紹

寫在前面

用戶進行了交互操作,現在要對頁面內容進行變更,此時可以通過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: truehttps://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` 訪問組件實例
  })
}

完整的導航解析流程

  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 的回調函數。

路由元信息

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.router.currentRoute===this.router.currentRoute === 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
      }
    }
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章