一、vue-router是什麼?
要搞明白這個問題,你必須先了解什麼是單頁應用(或叫單頁Web應用),以下解釋引自百度百科:
單頁Web應用(single page web application,SPA),就是隻有一張Web頁面的應用,是加載單個HTML 頁面並在用戶與應用程序交互時動態更新該頁面的Web應用程序。
單頁應用不存在頁面跳轉,它本身只有一個HTML頁面。我們傳統意義上的頁面跳轉在單頁應用的概念下轉變爲了body
內某些元素的替換和更新,舉個例子,假如我們有下面的頁面index.html
:
<!DOCTYPE html>
<html>
<head>...</head>
<body>
<login-page/>
</body>
</html>
這裏現在加載的是登錄組件。隨後用戶進行了登錄,我們在登錄請求的回調函數中把body
內的組件login-page
替換成了歡迎頁組件welcome-page
,因此index.html
就變成了下面的樣子:
<!DOCTYPE html>
<html>
<head>...</head>
<body>
<welcome-page/>
</body>
</html>
顯然index.html
仍然是它本身,但是由於用戶操作,它的整個body的內容從登錄組件變成了歡迎頁組件,我們實現了傳統意義上的頁面跳轉。但是要注意,由於當前加載的仍然是原來的HTML頁面,因此瀏覽器不會完全重載,已下載的公共資源(如框架代碼、普通的js代碼等)不會被重新執行。瀏覽器只會下載與新的組件相關的js代碼到本地執行,繼而完成頁面內容的更新。
這樣,從視覺上,頁面已經進行了跳轉。但實際上,頁面只是隨着用戶操作,實現了局部內容更新。所以你可以看到,相對於傳統的多頁面應用,單頁應用有着極高的“跳轉速度”,因此備受歡迎。
而vue-router
就是vue
框架下管理如何進行頁面替換和更新的組件。藉助vue-router
,你可以清晰地掌控整個頁面內容更新的過程,擁有像瀏覽器地址欄一樣強大的前進和回退功能。
router
的中文釋義爲“路由”,它是計算機網絡中非常重要的概念,表示分組從源到目的地時,決定端到端路徑的網絡範圍的進程。換句話說,就是分組數據包從源到目的地,經歷了哪些網絡節點。在單頁應用中,它表示頁面的更新過程中所經歷的路徑變化。由於沒有頁面跳轉,因此路由無法直接映射到頁面地址上,而是通過地址後面的hash值來體現的,舉個例子,下面可能是登錄頁的地址:
https://xxxx/index.html#/login
#
後面的值我們稱爲哈希值,即/login
,它表示我們當前處在index.html
頁面內的login
路徑下。當用戶執行了登錄後,頁面地址可能變化爲:
https://xxxx/index.html#/welcome
我們看到,頁面仍然是index.html
,但是它後面的哈希值卻變成了/welcome
,這表示頁面內部已經切換到了/welcome
所對應的組件。而我們的模板可能是這樣寫的:
<!DOCTYPE>
<html>
// 引入vue、vue-router以及登錄、歡迎頁等組件
<head>
...
</head>
<body>
<div id="app">
<router-view/>
</div>
<script>
const router = new VueRouter({ // 定義路由
routes: [
{ path: '/', redirect: '/login' }, //默認打開的頁面
{ path: '/login', component: login-page },
{ path: '/welcome', component: welcome-page }
]
})
const app = new Vue({
router // 向vue實例導入路由
}).$mount('#app');
</script>
</body>
</html>
html我們指定當地址中的哈希值爲/
時,重定向到/login
,而/login
和/welcome
則分別對應我們的登錄組件login-page
和歡迎頁組件welcome-page
。<router-view/>
是vue-router
使用的佔位組件,它將根據哈希值動態被替換爲對應的組件。
因此當你在地址欄輸入https://xxxx/index.html
,會首先匹配到空路由/
,隨後被重定向到/login
,vue-router
會將該路由對應的組件login-page
替換到佔位組件<router-view/>
的位置。當用戶執行了登錄,並執行類似以下的操作時:
this.$axios.post('xxx', data).then(res => {
this.$router.push('/welcome');
})
該語句向路由堆棧中添加了新的值/welcome
,因此vue-router
會將對應的welcome-page
組件替換到佔位組件<router-view/>
。從視覺上,登錄頁被替換成了歡迎頁,而內部機制上,就是完成了一次組件替換,沒有發生頁面重載。
vue-router
的作用就是對單頁應用中的上述過程進行管理,下面我們簡單來看一下它的基本用法。
二、基本用法
關於vue-router
的基本語法,vue-router官網上有詳盡的講解,有需要的可以移步官網進行深入學習。這裏我們進行一下大致的介紹。
1. 起步
要使用vue-router
,首先需要安裝和引入它。
npm install vue-router --save // 安裝
impoer VueRouter from 'vue-router'; // 引入
const router = new VueRouter({
routes: [
{ path: '/login', component: login-page },
{ path: '/welcome', component: welcome-page },
... // 爲每個路由指定對應的組件
]
})
const app = new Vue({
router // 向vue實例導入路由
}).$mount('#app');
現在我們已經定義好了路由映射規則,並在vue實例中引入了它。當我們修改路由堆棧中的值時,vue-router
就會按照這套規則將抽象組件<router-view/>
替換爲對應的組件。
此時的HTML模板可能像下面一樣:
<body>
<div id="app">
<router-view/>
</div>
</body>
那我們怎麼觸發路由的變化呢?
當我們在創建vue實例並傳入router實例對象時,vue會自動將這個router對象作爲vue實例的$router
屬性,即:
const app = new Vue({
router // 向vue實例導入路由
}).$mount('#app');
// app.$router => vue-router實例對象
變量app是一個vue實例,接下來我們便可以通過它的$router
屬性去觸發路由變化了。比如我們想修改路由,可以通過push
或replace
方法:
app.$router.push('/welcome');
// app.$router.replace('/welcome');
push方法會將前一個路由/login
保存在堆棧中,而replace則是直接替換上一個路由,因此在路由回退時兩者的行爲是不同的。
上面的語法稱爲編程式的導航,也就是通過js代碼進行導航。除此之外,還可以通過路由組件的方式:
<p>
<!-- 使用 router-link 組件來導航. -->
<!-- 通過傳入 `to` 屬性指定鏈接. -->
<router-link to="/login">Go to login-page</router-link>
<router-link to="/welcome">Go to welcome-page</router-link>
</p>
這兩個router-link
組件是由vue-router
定義的,它們在頁面上會被渲染爲一個a標籤。每個組件帶有一個to
屬性,表示點擊該組件需要跳轉的路由地址,點擊該組件的行爲與執行push
方法的結果是一致的。這種方式常用於導航欄。
2. 動態路由
在實際應用中,我們可能需要將多個路由地址映射到同一個組件。比如我們現在有一個商品列表,點擊某個商品就會進入該商品的詳細信息頁面。由於商品詳細信息的格式是一致的,因此我們只編寫了一個組件goods-detail
。
假設商品列表的路由地址爲/goods
,而/goods/pants
,/goods/shoes
這樣的路由地址都是某個商品對應的詳細信息頁面,它們使用的都是同一個組件goods-detail
。
這樣的需求我們就需要用到動態路由了。此時我們可以像下面一樣定義路由規則:
const router = new VueRouter({
routes: [
// 動態路徑參數 以冒號開頭
{ path: '/goods/:type', component: goods-deatil }
]
})
現在只要是符合/goods/xxx
這種模式的路由,都會被這個規則捕獲,加載goods-detail
組件,並且路由中商品的類型被定義爲了type屬性。我們可以在goods-detail
組件中通過路由參數對象$route
獲取這個值:
goods-detail
<template>
<div>
<p>{{ $route.params.type }}</p>
</div>
</template>
當路由值爲/goods/shoes
時,組件內部會渲染成:
<div>
<p>shoes</p>
</div>
注意,$router
與$route
並不是同一個屬性,前者是路由實例對象本身,而後者是該實例對象對應的參數對象,一般來說,操作路由應該使用$router
,如push、replace等,獲取參數應該用$route
,如獲取params值。
3. 嵌套路由
當多個路由地址的前綴路徑相同時,嵌套路由可以減少路由規則定義的複雜性。比如,如果沒有嵌套路由,我們可能會像下面一樣定義一組路由:
const router = new VueRouter({
routes: [
{ path: '/goods/detail', component: goods-deatil },
{ path: '/goods/pay', component: goods-pay },
{ path: '/goods/feedback', component: goods-feedback }
]
})
使用嵌套路由,你可以寫成下面的格式:
const router = new VueRouter({
routes: [
// 動態路徑參數 以冒號開頭
{ path: '/goods',
children: [
{ path: 'detail', component: goods-deatil },
{ path: 'pay', component: goods-pay },
{ path: 'feedback', component: goods-feedback }
]
]
})
children屬性作爲/goods
的子路由存在,它定義的路由會被拼接到上一級路由後面,即/goods/detail
,/goods/pay
,/goods/feedback
。
4. 命名路由
在定義路由規則時,你可以爲路由指定一個名字,這樣你就可以不用在每次切換路由時指定路由地址了,如:
const router = new VueRouter({
routes: [
{ path: '/goods/detail',
name: 'detail',
component: goods-deatil },
]
})
現在'/goods/detail'
這個路徑已經被命名爲detail
,因此你可以像下面一樣跳轉到這個路由:
<router-link :to="{name: 'detail', params: { id: '123'}}">detail</router-link>
// 或
this.$router.push({name: 'detail', params: { id: '123' }})
5. 命名視圖
一個頁面可以存在多個router-view
佔位組件,那麼當切換一個路由時,如何爲每個router-view
指定要渲染的組件呢?答案就是使用命名視圖,爲每個router-view
指定一個名字,然後分別定義組件,如:
const router = new VueRouter({
routes: [
{ path: '/goods',
component: {
default: goods-content, // 默認視圖
nav: goods-nav
} },
]
})
而頁面模板可能像下面這樣:
<div>
<router-view/>
<router-view name="nav"/>
</div>
當執行this.$router.push('/goods')
時,第一個router-view
會被替換爲goods-content
組件,而第二個會被替換爲goods-nav
組件。
6. 重定向和別名
重定向,顧名思義,就是將一個路由定向到另一個路由,我們在處理根路由時常常會用到。比如在最開始的例子中,我們只有/login
和/welcome
這兩個組件,沒有/
對應的組件。這樣當用戶在地址欄輸入https://xxxx/index.html
或https://xxxx/index.html#/
時,頁面會顯示爲白頁。
假如我們希望讓/
自動加載/login
的組件,可能需要下面的額外定義:
{ path: '/', component: login-page },
但我們想的是這時應用應該自動將路由直接置爲/login
,於是我們會這樣寫:
{ path: '/', redirect: '/login' },
它表示路由地址/
現在將會被重定向到/login
,所以當用戶輸入https://xxxx/index.html
,地址欄的值會直接變成https://xxxx/index.html#/login
,這就是一次路由重定向。
別名則是給某個路由另起一個名字。如:
{
path: '/login',
component: login-page,
alias: '/userLogin'
}
現在輸入https://xxxx/index.html#/login
和https://xxxx/index.html#/userLogin
都會匹配該路由。加載login-page
組件。
7. 路由傳參
當我們在路由跳轉時向路由組件傳遞了參數,除了可以像上面一樣使用$route
對象來獲取外,還可以通過props
屬性,將參數轉化爲props。如:
{ path: '/user/:id', component: User, props: true },
props: true
意味着現在與路由相關的參數都被作爲props
傳入組件,因此你可以這樣獲取參數:
const User = {
props: ['id'],
template: '<div>User {{ id }}</div>'
}
路由中的id參數將作爲props中的id屬性傳入。
除了使用布爾值,props還可以使用對象或函數。
傳入對象時,它會原樣被設置爲組件屬性:
{ path: '/user/:id', component: User, props: { state: 'active' } }
組件內:
const User = {
props: ['state'],
template: '<div>User {{ state }}</div>'
}
傳入函數時,可以基於路由參數對象,返回更復雜的參數:
{ path: '/search', component: SearchUser, props: (route) => ({ query: route.query.q }) }
URL/search?q=vue
會將{query: 'vue'}
作爲屬性傳遞給 SearchUser 組件。
8. History模式
vue-router
默認使用hash模式進行路由導航,因此在切換路由時,頁面並不會跳轉。但是有人可能覺得,在url中添加一個#
看起來並不美觀,希望既可以不重載頁面,又可以不在地址中出現醜陋的#
,HTML5的History接口爲這種需求提供了可能。
這種模式的實現需要後端的配合,比如對於http://yoursite.com/user/id
這樣的地址,我們希望它加載index.html
頁面,就需要在後端配置好,否則就會返回404頁面。
前端收到index.html頁面後,自行根據url進行路由導航。想了解具體的實現,請參考MDN - History。
總結
本文只是關於vue-router
的基本語法介紹,還有一些進階用法沒有涉及,包括導航守衛、路由元信息、過渡動效、數據獲取、滾動、路由懶加載等,需要進一步學習請參考vue-router官網。