用
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(...)
。
- 路由傳參
傳參有兩種方式,分別是params
和query
,比如我想在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.push
、 router.replace
和 router.go
跟 window.history.pushState
、 window.history.replaceState
和 window.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
}
]
}
}
因爲 User
和 Article
都是 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
// 一般用於離開頁面之前,用戶表單未提交或者未完成一些操作時,給用戶的一些提示信息
}
完整的導航解析流程:
- 導航被觸發
- 在失活的組件裏調用離開守衛
- 調用全局的 beforeEach 守衛
- 在重用的組件裏調用 beforeRouteUpdate 守衛 (2.2+)
- 在路由配置裏調用 beforeEnter
- 解析異步路由組件
- 在被激活的組件裏調用 beforeRouteEnter
- 調用全局的 beforeResolve 守衛 (2.5+)
- 導航被確認
- 調用全局的 afterEach 鉤子
- 觸發 DOM 更新
- 用創建好的實例調用 beforeRouteEnter 守衛中傳給 next 的回調函數
7、路由元信息
定義路由的時候可以配置 meta 字段:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
children: [
{
path: 'bar',
component: Bar,
// a meta field
meta: { requiresAuth: true }
}
]
}
]
})