vue-router嵌套子路由實際使用


前端路由的定義

在spa流行之前,前端路由是沒有的;而像java之類的后台語言很早就有了,后端路由一般就是定義一系列的訪問地址規則,路由引擎根據這些規則匹配並找到對應的處理頁面,然后將請求轉發給頁面進行處理。
在spa應用中,前端路由是直接找到與地址匹配的一個組件或對象並將其渲染出來。改變瀏覽器地址而不向服務器發出請求有兩種做法,一是在地址中加入#以欺騙瀏覽器,地址的改變是由於正在進行頁內導航;二是使用HTML5的window.history功能,使用URL的Hash來模擬一個完整的URL。將單頁程序分割為各自功能合理的組件或者頁面,路由起到了一個非常重要的作用。它就是連接單頁程序中各頁面之間的鏈條。

路由與導航

單頁式應用是沒有“頁”的概念的,更准確地說,Vue.js是沒有頁面這個概念地,Vue.js地容器就只有組件。但我們用vue-router配合組件又會形成各種的“頁面”,那么我們可以這樣來約定和理解:
1.頁面是一個抽象的邏輯概念,用於划分功能場景
2.組件是頁面在Vue的具體實現方式

router-view

渲染路徑匹配到的視圖組件,它還可以內嵌自己的router-view
這里我主要記錄下在實際項目中,如何使用命名路由和嵌套命名視圖實現布局。下圖是我們需要實現的效果(這個效果標記A)index.vue:

vue-router嵌套子路由實際使用-青梅煮碼

很簡單吧,我相信每個人都可以設計出這樣布局的路由配置;不過,我這里有2個需求:
1.我希望main + aside這整塊區域可以跳轉路由;什么意思呢,就是從A可以跳轉到B(也就是下面這張圖)container.vue:

vue-router嵌套子路由實際使用-青梅煮碼

2.我希望main和aside兩塊是獨立的;也就是說,main里可以跳轉到其他路由,aside也可以跳轉到其他路由;(當然也可以只跳轉一個區域的路由,另一個路由不變)也就是從A直接跳轉到C(看下圖)article-detail.vue:

vue-router嵌套子路由實際使用-青梅煮碼

我們都知道,用vue-cli做項目,都會有一個頂層路由入口router-view寫在app.js里面;很顯然我們這里的header,main,aside,footer都在這個頂層入口里;我們先來實現一下需求1,需求1很簡單,就是在頂層入口里加一個子路由;但是考慮到需求二的原因,index.vue里面需要提前加入兩個命名視圖來渲染首頁,以便於需求二獨立渲染main和aside這兩個部分:
router.js

import Vue from 'vue'  
import Router from 'vue-router'  
Vue.use(Router)  
let router = new Router({
  path: '/',
  name: 'index',
  component: () => import ('@/views/index.vue')
  children: [
    {
      path: '',
      components: {
        main: () => import('@/views/main.vue'),
        aside: () => import('@/views/aside.vue')
      }
    },
    {
      path: 'container',
      component: () => import ('@/views/container.vue')
    }
  ]
})

index.vue

<template>
  <myheader></myheader>
  <router-view></router-view>
  <router-view name="main"></router-view>
  <router-view name="aside"></router-view>
  <myfooter></myfooter>
</template>

實現需求二就和根路由設置一樣了,在路由里使用兩個組件來渲染即可:

import Vue from 'vue'  
import Router from 'vue-router'  
Vue.use(Router)  
let router = new Router({
  path: '/',
  name: 'index',
  component: () => import ('@/views/index.vue'),
  children: [
    {
      path: '',
      components: {
        main: () => import('@/views/main.vue'),
        aside: () => import('@/views/aside.vue')
      }
    },
    {
      path: 'container',
      component: () => import ('@/views/container.vue')
    },
    {
      path: 'article-detail',
      components: {
        main: () => import('@/views/article-detail.vue'),
        aside: () => import('@/views/aside.vue')
      }
    }
  ]
})

除了上面這種做法,我再貼一個實現相同功能的代碼塊:

路由配置:

let router = new Router({
mode: 'history',
scrollBehavior: () => ({y: 0}),
routes: [
  {
    path: '/',
    name: 'home',
    redirect: '/home',
    component: () => import('@/views/home.vue'),
    children: [
      {
        path: 'home',
        component: () => import('@/views/mainAndAside.vue'),
        children: [
          {
            // 這里的path為空,當父組件匹配不到路由時,默認就會渲染這個子路由
            path: '',
            meta: {
              title: '首頁'
            },
            components: {
              main: () => import('@/views/main.vue'),
              aside: () => import('@/views/aside.vue')
            }
          }
        ]
      },
      {
        path: 'container',
        component: () => import('@/views/container.vue'),
      },
      {
        path: 'article/detail/:id',
        component: () => import('@/views/mainAndAside.vue'),
        props: true,
        children: [{
          path: '',
          meta: {
            title: '詳情頁'
          },
          components: {
            main: () => import('@/views/articleMain.vue'),
            aside: () => import('@/views/articleAside.vue')
          },
          props: {
            main: true,
            aside: false
          }
        }]
      },
    ]

再看一看兩個核心組件的代碼:

home.vue

<template>
  <home-layer>
      <el-col slot="header">
        <myheader></myheader>
      </el-col>
      <router-view slot="main"></router-view>
      <div slot="footer">
        <myfooter></myfooter>
      </div>
      <go-top></go-top>
  </home-layer>
</template>
.......

mainAndAside.vue(這里用了element-ui)

<template>
  <el-row class="main-wrap" :gutter="20">
    <el-col class="aside" ref="aside" :md="8"  :xl="6" :sm="24">
      <div ref="asideWrap" class="aside-wrap">
        <router-view name = "aside"></router-view>
      </div>
    </el-col>
    <el-col class="main" :md="16"  :xl="18" :sm="24">
      <router-view name = "main" :key="key"></router-view>
    </el-col>
  </el-row>
</template>

其他無關緊要的組件,就不展示了。上面這種做法,更加靈活的控制了布局,而不是將三個router-view並列排在一起,而是以一個未命名的router-view作為總入口,然后在這個組件里再設置兩個命名視圖;這樣就可以只渲染總入口的router-view,也可以同時渲染總入口的router-view和子組件的兩個命名視圖;完全看路由的配置了,很靈活。

全局路由鈎子之beforeEach和afterEach

簡單看一下,實際應用中的代碼:

let loadingInstance = null
// 路由全局前置守衛
router.beforeEach((to, from, next) => {
  loadingInstance = Loading.service({lock: true})
  let token = getCookie('token')
  // 修改網頁標題
  window.document.title = to.meta.title
  // token存在的情況(代表用戶登錄成功過)
  if (token) {
    if (!String(store.getters.token)) {
      store.commit('setToken', token)
    }
    if (String(store.getters.nickname) === '') {
      // 當vuex中沒有用戶數據時,從后台獲取
      store.dispatch('getInfo')
    }
    forbidRedirect(to, next)
  } else {
    // 如果token不存在;判斷路由是否需要登錄權限
    if (to.meta.requireAuth) {
      next({
        path: '/login',
        query: { redirect: to.fullPath }
      })
    } else {
      next()
    }
  }
})
 
// 路由全局后置守衛
router.afterEach((to, from) => {
  loadingInstance.close()
})

在beforeEach中根據token判斷用戶是否登錄,如果登錄了,則查看vuex中有木有用戶信息,沒有則在vuex中執行getInfo的action獲取用戶信息;如果未登錄,則判斷將要跳轉的目標路由,是否需要登錄才能跳轉;如果是,則使用next()導航到登錄頁,否則,正常跳轉;另外,在beforeEach里,加載一個loading動畫,在afterEach中關閉這個loading動畫。

history模式

當我們把路由配置成history模式后,假如用戶點擊/index上的http://localhost/index)。如果我們直接在瀏覽器輸入http://localhost/index,你會驚奇的發現瀏覽器會出現404的錯誤!
這是由於直接在瀏覽器中輸入http://localhost/home,瀏覽器就會直接將這個地址請求發送至服務器,先由服務器處理路由,而客戶端路由的啟動條件是要訪問/index.html,這樣的話客戶端路由就完全失效了!
解決的辦法是將所有發送到服務器的請求利用服務端的URLRewrite模板重新轉發給/index.html,啟動VueRouter進行處理,而瀏覽器地址欄的URL保持不變。
這個問題在開發環境下是不會出現的,因為我們在開發環境中使用的是webpack的DevServer,DevServer是對這個問題進行了處理的,只要打開vue-cli(2.X版本)生成的項目中buid目錄下的webpack.dev.config.js找到devServer配置屬性就可以見到:

devServer: {
  historyApiFallback: {
    rewrites: [
      { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
    ]
  }
}

而當我們部署到生產環境時,就需要在web服務器上進行一些簡單配置以支持Fallback。
我只用到過nginx服務器,就以這個為例吧:

location / {
  try_files $uri $uri/ /index.html;
}

一旦我們進行了上述配置,你的服務器就不會再返回404錯誤頁面,因為對於所有路徑都會返回index.html文件。為了避免發生這種情況,應該在Vue應用里面覆蓋所有的路由情況,然后再給出一個404頁面。

const router = new VueRouter({
  mode: 'history',
  routes: [
     .....,
     .....,
     .....,
     // 這個路由應該放在最后面,否則會覆蓋其他已有的路由
     { path: '*', component: 404.vue}
 
  ]
})


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM