vue-router是vue項目的重要組成部分,用於構建單頁應用。單頁應用是基於路由和組件的,路由用於設定訪問路徑,並將路徑和組件映射起來。路由的本質就是創建url和頁面之間的映射關係。html
hash模式是vue-router的默認模式。hash指的是url描點,當描點發生變化的時候,瀏覽器只會修改訪問歷史記錄,不會訪問服務器從新獲取頁面。所以能夠監聽描點值的變化,根據描點值渲染指定dom。vue
能夠經過location.hash = "/hashpath"
的方式修改瀏覽器的hash值。node
能夠經過監聽hashchange事件監聽hash值的變化。nginx
window.addEventListener('hashchange', () => { const hash = window.location.hash.substr(1) // 根據hash值渲染不一樣的dom })
hash模式下,url可能爲如下形式:web
http://localhost:8080/index.html#/book?bookid=1
vue-router
上面的url中既有#又有?,會讓url看上去很奇怪,所以,能夠使用history模式,在此模式下,url會以下面所示:express
http://localhost:8080/book/1
api
H5的history對象提供了pushState和replaceState兩個方法,當調用這兩個方法的時候,url會發生變化,瀏覽器訪問歷史也會發生變化,可是瀏覽器不會向後臺發送請求。瀏覽器
// 第一個參數:data對象,在監聽變化的事件中可以獲取到 // 第二個參數:title標題 // 第三個參數:跳轉地址 history.pushState({}, "", '/a')
能夠經過監聽popstate事件監聽history變化,也就是點擊瀏覽器的前進或者後退功能時觸發。服務器
window.addEventListener("popstate", () => { const path = window.location.pathname // 根據path不一樣可渲染不一樣的dom })
當使用hash模式的時候,若是手動刷新瀏覽器,頁面也可以正常顯示。可是在history模式下,刷新瀏覽器就會出現問題。
如訪問http://localhost:8080/book/1
時,服務端會查找是否有相應的html可以匹配此路徑,在單頁應用下,服務端只有一個index.html,因此此時匹配不到,會提示404。針對這個問題,須要服務端進行history模式支持。
在nodejs服務中,能夠引入connect-history-api-fallback
插件:
const path = require('path') // 導入處理 history 模式的模塊 const history = require('connect-history-api-fallback') // 導入 express const express = require('express') const app = express() // 註冊處理 history 模式的中間件 app.use(history()) // 處理靜態資源的中間件,網站根目錄 ../web app.use(express.static(path.join(__dirname, '../web'))) // 開啓服務器,端口是 3000 app.listen(3000, () => { console.log('服務器開啓,端口:3000') })
在nginx服務中,能夠以下方式修改配置文件,添加history模式支持:
location / { root html; index index.html index.htm; #新添加內容 #嘗試讀取$uri(當前請求的路徑),若是讀取不到讀取$uri/這個文 件夾下的首頁 #若是都獲取不到返回根目錄中的 index.html try_files $uri $uri/ /index.html; }
VueRouter核心是,經過Vue.use註冊插件,在插件的install方法中獲取用戶配置的router對象。當瀏覽器地址發生變化的時候,根據router對象匹配相應路由,獲取組件,並將組件渲染到視圖上。
主要有三個重要點:
能夠利用Vue.mixin混入聲明周期函數beforeCreate,在beforeCreate函數中能夠獲取到Vue實例上的屬性並賦值到Vue原型鏈上。
_Vue.mixin({ beforeCreate () { if (this.$options.router) { _Vue.prototype.$router = this.$options.router } } })
hash模式下:
history模式下:
完整版
// 存儲全局使用的Vue對象 let _Vue = null class VueRouter { // vue.use要求plugin具有一個install方法 static install (Vue) { // 判斷插件是否已經安裝過 if (VueRouter.install.installed) { return } VueRouter.install.installed = true _Vue = Vue // 將main文件中實例化Vue對象時傳入的router對象添加到Vue的原型鏈上。 _Vue.mixin({ beforeCreate () { if (this.$options.router) { _Vue.prototype.$router = this.$options.router } } }) } constructor (options) { this.options = options // 用於快速查找route this.routeMap = {} this.data = _Vue.observable({ current: window.location.hash.substr(1) }) this.init() } init () { this.createRouteMap() this.initComponents(_Vue) this.initEvent() } createRouteMap () { // 遍歷全部的路由規則 吧路由規則解析成鍵值對的形式存儲到routeMap中 this.options.routes.forEach(route => { this.routeMap[route.path] = route.component }) } initComponents (Vue) { // 註冊router-link組件 Vue.component('router-link', { props: { to: String }, methods: { clickHandler (e) { // 修改hash location.hash = this.to // 修改current,觸發視圖更新 this.$router.data.current = this.to e.preventDefault() } }, render (h) { return h('a', { attrs: { href: this.to }, on: { click: this.clickHandler } }, [this.$slots.default]) } }) const that = this // 註冊router-view插件 Vue.component('router-view', { render (h) { const component = that.routeMap[that.data.current] return h(component) } }) } initEvent () { // 在hash發生更改的時候,修改current屬性,觸發組件更新 window.addEventListener('hashchange', () => { this.data.current = window.location.hash.substr(1) }) } } export default VueRouter