Vue-router的使用和一些技巧

Vue.js + Vue Router創建單頁應用,是非常簡單的。使用 Vue.js ,我們已經可以通過組合組件來組成應用程序,當你要把Vue Router 添加進來,我們需要做的是,將組件 components 映射到路由 routes,然後告訴 Vue Router 在哪裏渲染它們。

1、簡單配置

首先在入口 main.js 文件中引入路由

//使用模塊化機制編程,導入Vue和VueRouter,要調用 Vue.use(VueRouter)
import Vue from 'vue';
import VueRouter from 'vue-router';
import App from './App.vue';

Vue.use(Router)  //Vue全局使用Router

// 1. 定義 (路由) 組件,可以從其他文件 import 進來
import Home from './components/Home.vue'
import About from './components/About.vue'

// 2. 定義路由
// 每個路由應該映射一個組件。 其中"component" 可以是通過 Vue.extend() 創建的
// 組件構造器,或者只是一個組件配置對象。
const routes = [
	// 配置路由,因爲會有多個,所以是個數組
    {
        path: '/',
        redirect: '/home' // 如果是根路徑,則重定向到 /home
    },
    // 可以通過 鏈接路徑 或者 路由別名 鏈接到一個路由
    {
        path: '/home', // 連接路徑 <router-link to="/home">Home</router-link>
        name: 'home', // 給路由設置名稱,router-link 的 to 屬性傳一個對象,<router-link :to="{ name: 'home'}">Home</router-link>
        component: Home //  對應的組件模板
    },
    {
        path: '/about',
        name: 'about',
        component: About
    }
]

// 3. 創建 router 實例,然後傳 `routes` 配置
// 你還可以傳別的配置參數, 不過先這麼簡單着吧。
const router = new VueRouter({
  routes // (縮寫) 相當於 routes: routes
})

// 4. 創建和掛載根實例。
// 記得要通過 router 配置參數注入路由,
// 從而讓整個應用都有路由功能
new Vue({
    router,
    render: h => h(App)
}).$mount('#app')

然後在 App.vue 中設置導航和路由出口

<template>
    <div>
        <!-- 使用 router-link 組件來導航. -->
        <!-- 通過傳入 `to` 屬性指定鏈接. -->
        <!-- <router-link> 默認會被渲染成一個 `<a>` 標籤 -->
        <router-link to="/home">Home</router-link>
        <router-link to="/about">About me</router-link>
        <!-- 路由出口 -->
        <!-- 路由匹配到的組件將渲染在這裏 -->
        <router-view></router-view>
    </div>
</template>

<script>
export default {
    name: "App",
};
</script>

2、路由模式

瀏覽器地址欄輸入http://localhost:8000(webpack開啓熱更新),頁面會自動跳轉至http://localhost:8000/#/home,因爲我們定義路由時配置了服務器根路徑重定向到/home,細心的你還會發現服務器根目錄home之間多了一個#號,這是因爲vue-router 默認 hash 模式 —— 使用 URL 的 hash 來模擬一個完整的 URL,於是當 URL 改變時,頁面不會重新加載。

但是hash看起來就像無意義的字符排列,不太好看也不符合我們一般的網址瀏覽習慣。

所以,如果不想要很醜的 hash,我們可以使用路由的 history 模式,這種模式充分利用 history.pushState API 來完成 URL 跳轉而無須重新加載頁面:

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

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

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

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

但一般我們不會在每次用戶輸入錯誤的 URL 都返回同一個index.html頁面,不然用戶可能還不會意識到自己輸入的是一個錯誤的 URL,我們希望給他一個友好的提示,爲此提供一個漂亮的404頁面,告訴用戶他打開了一個美麗的錯誤。
vue-router也爲我們提供了這樣的機制:

{
   path:'*',
   component:Error
}

這裏的path: '*'就是找不到頁面時的配置,component組件一個Error.vue的文件。

3、路由跳轉和傳參

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

注意: 在 Vue 實例內部,可以通過 $router 訪問路由實例,通過 this.$route 訪問當前路由。
this.$router 相當於一個全局的路由器對象,包含了很多屬性和對象(比如 history 對象),任何頁面都可以調用其 push(), replace(), go() 等方法。
this.$route 表示當前路由對象,每一個路由都會有一個 route 對象,是一個局部的對象,可以獲取對應的 name, path, params, query 等屬性。

router.push(location, onComplete?, onAbort?)

  • 路由跳轉
// 聲名式
<router-link to="/home">Home</router-link> // 路徑字符串
<router-link :to="{path: '/home'}">Home</router-link> // 對象
<router-link :to="{name: 'home'}">Home</router-link> // 命名的路由

// 編程式
this.$router.push('home') // 字符串
this.$router.push({ path: '/home' }) // 對象
this.$router.push({ name: 'home'}) // 命名的路由

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

  • 路由傳參
    傳參有兩種方式,分別是 paramsquery,比如我想在URL中傳入用戶ID:
params請求方式: '/home/1' //訪問時,可以通過 this.$route.params.userId 獲取到參數值
query請求方式: '/home/?userId=1' //訪問時,可以通過 this.$route.query.userId 獲取到參數值

其中使用 `params` 方式需要事先在路由定義中配置相應的傳參形式, `query` 則不需要:
const routes = [
    {
        path: '/home/:userId', // 如果要傳多個參數,比如還要增加一個articleId,直接以相同的形式拼接即可/home/:userId/:articleId
        name: 'home',
        component: Home,
    }
}

可以說 params 相當於 POST 請求,參數不會在地址欄中顯示。而query 相當於 GET 請求,頁面跳轉的時候,可以在地址欄看到請求的參數。

接下對比一下兩種傳參方式:

// query
this.$router.push('home/?userId=1') // 字符串
this.$router.push({ path: '/home', query: {userId: 1} }) // 對象
this.$router.push({ name: 'home', query: {userId: 1} }) // 命名的路由

// params
this.$router.push('home/1') // 字符串
this.$router.push({ path: '/home', params: {userId: 1} }) // 這裏的 params 不生效
// 注意:如果使用了 path,params 會被忽略,因爲 params 只能用 name 來引入路由,而不同於上述例子中的 query,此時你需要提供路由的 name 或手寫完整的帶有參數的 path:
this.$router.push({ path: '/home/1' }) // { path: `/home/${userId}` }
this.$router.push({ name: 'home', params: {userId: 1} }) // 命名的路由

以上規則也適用於 router-link 組件的 to 屬性。

router.replace(location, onComplete?, onAbort?)

router.push 很像,唯一的不同就是,router.push會向 history 棧添加一個新的記錄,所以當用戶點擊瀏覽器後退按鈕時,則回到之前的 URL。而 router.replace 不會向 history 添加新記錄,而是跟它的方法名一樣 —— 替換掉當前的 history 記錄。

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

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

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

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

// 如果 history 記錄不夠用,那就默默地失敗唄
router.go(-100)
router.go(100)

router.pushrouter.replacerouter.gowindow.history.pushStatewindow.history.replaceStatewindow.history.go 好像, 實際上它們確實是效仿 window.history API 的。

經過以上講解,我們知道可以在組件內部可通過 this.$route 獲取當前路由對象的屬性,但這樣意味着我們的組件必須跟 Vue-router 偶合在一起,從而只能在某些特定的 URL 上使用,限制了其靈活性。
那麼有沒有更好的辦法,使我們的組件既可以使用路由又可以作爲單純的組件使用呢?答案是肯定的,請看下一節。

4、路由props傳參解耦

在定義路由時,我們需要在對應的配置項中添加一個 props 屬性,設置爲 ture 時,我們在對應組件中可以通過 props: ['userId'] 獲取路由中的參數(userId參數)值。

const routes = [
    {
        path: '/home/:userId',
        name: 'home',
        component: Home,
        props: ture
    }
}

以上 props 屬性值是布爾值,其實它還可以是一個對象或者回調函數。
這裏講解下回調函數,它傳入的參數是當前 route 路由對象,即第3節中的this.$route,然後我們就可以靈活的獲取到所需要的參數了:

// query: http://localhost:8000/home?userId=1&articleId=2
const routes = [
    {
        path: '/home',
        name: 'home',
        component: Home,
        props: (route) => ({
            userId: route.query.userId,
            articleId: route.query.articleId
        })
    }
}

// params: http://localhost:8000/home/1/2
const routes = [
    {
        path: '/home/:userId/:articleId',
        name: 'home',
        component: Home,
        props: (route) => ({
            userId: route.params.userId,
            articleId: route.params.articleId
        })
    }
}

這樣寫的好處是,可以使你的組件可複用性更高,它依賴的參數值,我們完全可以通過 props 傳進去,而不需要依賴路由對象來獲取,這就是一個解耦的過程,所以比較推薦通過聲明 props 這種方式。

5、嵌套路由

實際生活中的應用界面,通常由多層嵌套的組件組合而成。同樣地,URL 中各段動態路徑也按某種結構對應嵌套的各層組件,例如:

/home/user                    		  /home/article
+------------------+                  +-----------------+
| Home             |                  | Home            |
| +--------------+ |                  | +-------------+ |
| | User         | |  +------------>  | | Article     | |
| |              | |                  | |             | |
| +--------------+ |                  | +-------------+ |
+------------------+                  +-----------------+

藉助 vue-router,使用嵌套路由配置,就可以很簡單地表達這種關係。
要在嵌套的出口中渲染組件,需要在路由定義中使用 children 配置:

const routes = [
    {
        path: '/home',
        name: 'home',
        component: Home,
        children: [
        	{
        		// 當 /home/user 匹配成功,
	            // User組件 會被渲染在 Home組件 的 <router-view> 中
	            path: 'user',
	            component: User
        	},
        	{
        		// 當 /home/article 匹配成功,
	            // Article組件 會被渲染在 Home組件 的 <router-view> 中
	            path: 'article',
	            component: Article
        	}
        ]
    }
}

因爲 UserArticle 都是 Home 的嵌套子路由,所以需要在 Home 組件模板中添加這兩個子路由的 <router-view> 路由出口,這樣當 URL 訪問 /home/user/home/article 時,對應子路由組件中的內容纔會被渲染到上一級路由的 <router-view> 中,我們可以認爲 <router-view> 就是一個佔位符。

你會發現,children 配置就是像 routes 配置一樣的路由配置數組,所以你可以嵌套多層路由。

要注意,以 / 開頭的嵌套路徑會被當作根路徑。 這讓你充分的使用嵌套組件而無須設置嵌套的路徑。

基於上面的配置,當你訪問一個不存在的子路由,如 /home/foo ,此時 Home 組件中的 <router-view> 是不會渲染任何東西的,這是因爲沒有匹配到合適的子路由。如果你想要渲染點什麼,可以提供一個 空的 子路由:

const routes = [
    {
        path: '/home',
        name: 'home',
        component: Home,
        children: [
        	{
        		// 當 /home 匹配成功,
	            // Other組件 會被渲染在 Home組件 的 <router-view> 中
	            path: '',
	            component: Other
        	},
        	// ...其他子路由
        ]
    }
}

6、路由鉤子

在某些情況下,當路由跳轉前或跳轉後、進入、離開某一個路由前、後,需要做某些操作,就可以使用路由鉤子來監聽路由的變化

全局路由鉤子

// 前置守衛
router.beforeEach((to, from, next) => {
    //會在任意路由跳轉前執行,next一定要記着執行,不然路由不能跳轉了
  console.log('router beforeEach')
  if (to.fullPath === '/app') {
    // next('/login')//不僅可以寫字符串還可以是一個對象
    next({path: '/login', replace})
  } else {
    next()
  }
})

// 解析守衛
router.beforeResolve((to, from, next) => {
  // 和全局前置守衛 router.beforeEach 類似,區別是在導航被確認之前, 同時在所有組件內守衛和異步路由組件被解析之後 ,解析守衛才被調用
  console.log("router beforeResolve")
  next();
});

// 後置鉤子
router.afterEach((to, from) => {
    // 會在任意路由跳轉後執行,沒有 next 函數也不會改變導航本身
    // 可以利用全局後置鉤子在導航結束後執行一些需要的操作
  console.log('afterEach')
})

路由獨享守衛
它是該路由獨享的,只有進入該路由時纔會執行這個守衛,其參數與全局前置守衛的方法參數是一樣的。

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

路由組件鉤子

beforeRouteEnter (to, from, next) {
    // 在渲染該組件的對應路由被 confirm 前調用
    // 不能獲取組件實例 this
    // 因爲當守衛執行前,組件實例還沒被創建
    next(vm => {
		// 通過回調方式獲取到該組件的實例,這裏的 vm 相當於 this
	})
},
beforeRouteUpdate (to, from, next) {
    // 在當前路由改變,但是該組件被複用時調用
    // 舉例來說,對於一個帶有動態參數的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉的時候,
    // 由於會渲染同樣的 Foo 組件,因此組件實例會被複用,而這個鉤子就會在這個情況下被調用。
    // 可以訪問組件實例 this

	// 可用於解決了不刷新頁面,改變路由參數時,mounted或者created生命週期不觸發,獲取不到參數變化問題(組件複用,生命週期只觸發一次)
},
beforeRouteLeave (to, from, next) {
    // 導航離開該組件的對應路由時調用
    // 可以訪問組件實例 this
	
	// 一般用於離開頁面之前,用戶表單未提交或者未完成一些操作時,給用戶的一些提示信息
}

完整的導航解析流程

7、路由元信息

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

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      children: [
        {
          path: 'bar',
          component: Bar,
          // a meta field
          meta: { requiresAuth: true }
        }
      ]
    }
  ]
})
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章