VueRouter的實作原理
- Hash模式和History模式的區別
- 表現形式的區別
- 原理的區別
- History 模式的使用
- node 環境下支持 history
- Nginx 下支持 history
- VueRouter 兩種模式的實作原理
- Hash 模式
- History 模式
- 實作思路
- VueRouter-install 方法實作
- 添加 VueRouter 的constructor
- createRouterMap
- initComponents
- 創建RouterLink 組件
- 報錯的解決
- 創建RouterView組件
- 在routerlink中添加點擊事件,修改地址
- initEvent
Hash模式和History模式的區別
不管哪種模式,前端路由都是客戶端路由的實作方式,也就是當路徑發生變化時,不會向服務器發送請求,是利用js監視路徑的變化,然后根據不同的地址渲染不同的內容,如果需要服務器內容,會發送Ajax請求來獲取,
表現形式的區別
- hash 模式
https://music.163.com/#/discover/toplist地址中會存在 # 號 - history 模式
https://music.163.com/discover/toplist地址中沒有# 類似于普通的地址,但是需要服務端配置支持
原理的區別
- hash 模式是基于錨點, 以及onhashchange 事件
- history 模式是基于 HTML5 中的 History API
- history.pushState() IE10 以后才支持
- history.replaceState() \
History 模式的使用
- History 需要服務器的支持
- 單頁應用中,如果重繪頁面,會向服務器發起請求,而服務器不存在這樣的地址就會回傳找不到該頁面從而出現404
- 在服務端應該除了靜態資源外都回傳單頁應用的 index.html
node 環境下支持 history
在 node 環境下,啟用對history模式的支持可以通過 connect-history-api-fallback 這個中間件來完成
// 匯入處理 history 模式的模塊
const history = require('connect-history-api-fallback')
// 匯入 express
const express = require('express')
const app = express()
// 注冊處理 history 模式的中間件
app.use(history())
Nginx 下支持 history
- 從官網下載 nginx 的壓縮包
- 把壓縮包解壓到 c 盤根目錄,c:\nginx-1.18.0 檔案夾
- 修改 conf\nginx.conf 檔案
運行nginx服務器基本指令
啟動
start nginx
重啟
nginx -s reload
停止
nginx -s stop
- 修改 conf\nginx.conf 檔案
location / {
root html;
index index.html index.htm;
#新添加內容
#嘗試讀取$uri(當前請求的路徑),如果讀取不到讀取$uri/這個檔案夾下的首頁
#如果都獲取不到回傳根目錄中的 index.html
try_files $uri $uri/ /index.html;
}
VueRouter 兩種模式的實作原理
Hash 模式
- URL 中 # 后面的內容作為路徑地址
- 監聽 hashchange 事件
- 根據當前路由地址找到對應組件重新渲染
History 模式
- 通過 history.pushState() 方法改變地址欄
- 監聽 popstate 事件
- 根據當前路由地址找到對應組件重新渲染
實作思路

從上圖,可以大致了解一下 VueRouter 這個類中的結構:
上半部分是屬性,下半部分是方法,其中+ 是實體方法,- 是靜態方法,
install 是用來實作Vue.use 插件機制的方法,
VueRouter-install 方法實作
要實作install方法,首先先分析一下該方法要做的事情:
- 判斷當前插件是否已經被安裝
- 把Vue建構式記錄到全域變數
- 把創建Vue實體時候傳入的router物件注入到所有的Vue實體上
let _Vue;
export default class VueRouter {
static install(Vue) {
// 1. 判斷當前插件是否已經被安裝
if(VueRouter.install.installed) return
VueRouter.install.installed = true
// 2. 把Vue建構式記錄到全域變數
_Vue = Vue
// 3. 把創建Vue實體時候傳入的router物件注入到所有的Vue實體上
// 利用混入讓所有的vue實體加載router
_Vue.mixin({
beforeCreate(){
// this.$options.name用來獲取vue實體 data以外的屬性
// new Vue( { router } )
if(this.$options.router) {
_Vue.prototype.$router = this.$options.router
}
}
})
}
}
添加 VueRouter 的constructor
VueRouter 的建構式要初始化三個屬性,分別是: options、data、routeMap,
- options 是路由的構造配置物件
- data 應該是一個回應式的物件,其中有一個屬性 current 用來記錄當前我們的路由地址,這里我們該如何才能創建一個回應式的物件呢?可以使用Vue的observable方法
- routeMap 中記錄了 options里的rules,rules決議出來 會以鍵值對的形式存在 routeMap中 ,key 就是路由地址,value 就是路由組件
constructor(options){
this.options = options
this.data = _Vue.observable({ current:'/' })
this.routeMap = {}
}
createRouterMap
接下來我們來實作VueRouter類中 createRouterMap 這個方法,它的作用就是把 options 中rules 路由規則決議出來以鍵值對的形式存盤在routeMap上,
createRouterMap() {
this.options.rules.forEach(route => this.routeMap[route.path] = route.component)
}
initComponents
下一步,來創建initComponents 方法,這個方法里我們要創建兩個組件,分別是:RouterLink 和 RouterView
創建RouterLink 組件
let _Vue;
export default class VueRouter {
static install(Vue) {
// 1. 判斷當前插件是否已經被安裝
if (VueRouter.install.installed) return
VueRouter.install.installed = true
// 2. 把Vue建構式記錄到全域變數
_Vue = Vue
// 3. 把創建Vue實體時候傳入的router物件注入到所有的Vue實體上
// 利用混入讓所有的vue實體加載router
_Vue.mixin({
beforeCreate() {
// this.$options.name用來獲取vue實體 data以外的屬性
// new Vue( { router } )
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
this.$options.router.init()
}
}
})
}
constructor(options) {
this.options = options
this.routeMap = {}
this.data = _Vue.observable({
current: '/'
})
}
createRouterMap() {
this.options.routes.forEach(route => this.routeMap[route.path] = route.component)
}
initComponents(Vue) {
// 創建RouterLink組件
Vue.component('router-link', {
props: {
'to': { type: String }
},
template: `<a :href="to"><slot></slot></a>`
})
}
init() {
this.createRouterMap()
this.initComponents(_Vue)
}
}
用自己的VueRouter 替換掉官方的運行后,發現報錯

報錯的意思是,運行時版本的Vue 不支持 tempalte 模板,需要打包的時候提前編譯,
如果要讓我們的template被支持可以使用完整版的Vue,完整包包含運行時和編譯器,體積比運行時版本大10k左右,程式運行的時候把模板轉換成render函式
@vue/cli 自動安裝的就是 運行時版本
報錯的解決
第一種方案——引入完整版Vue,可以在vue.config.js中 加入配置
module.exports = {
runtimeCompiler: true
}
第二種方案——使用render函式替換掉tempalte
render(h) {
return h('a', {
attrs: { href: this.to }
}, [this.$slots.default])
}
// template: `<a :href="to"><slot></slot></a>`
創建RouterView組件
// 記錄一下this
let self = this
Vue.component('router-view',{
render(h){
// routeMap以key value形式記錄了path和component
// data.current 記錄了當前頁面的path
return h(self.routeMap[self.data.current])
}
})
在routerlink中添加點擊事件,修改地址
為了能夠讓鏈接成功完成跳轉展示組件,我們需要對routerlink中的a標簽添加點擊事件
并且要在點擊的時候,把最新的path更新到router實體的current上.
我們借助于history的pushState方法 該方法會修改瀏覽器地址欄中的地址,但不會向服務器發起請求,并且還可以將新地址記錄在歷史中
Vue.component('router-link', {
props: {
'to': { type: String }
},
render(h) {
return h('a', {
attrs: { href: this.to },
on: { click: this.clickHandle }
}, [this.$slots.default])
},
methods: {
clickHandle(e) {
history.pushState({}, "", this.to)
// 把點擊的鏈接地址 更新到 current 上
this.$router.data.current = this.to
e.preventDefault()
}
}
// template: `<a :href="to"><slot></slot></a>`
})
initEvent
現在功能基本上已經差不多了,但是還存在一個小問題,就是當我們點擊瀏覽器的前進或者后退按鈕的時候,組件不能實作切換展示,主要思路就是通過添加popstate監聽地址變化,下面我們來完善該功能
initEvent(){
//
window.addEventListener("popstate",()=>{
this.data.current = window.location.pathname
})
}
完整代碼
let _Vue;
export default class VueRouter {
static install(Vue) {
// 1. 判斷當前插件是否已經被安裝
if (VueRouter.install.installed) return
VueRouter.install.installed = true
// 2. 把Vue建構式記錄到全域變數
_Vue = Vue
// 3. 把創建Vue實體時候傳入的router物件注入到所有的Vue實體上
// 利用混入讓所有的vue實體加載router
_Vue.mixin({
beforeCreate() {
// this.$options.name用來獲取vue實體 data以外的屬性
// new Vue( { router } )
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
console.log(this.$options.router.init);
this.$options.router.init()
}
}
})
}
constructor(options) {
this.options = options
this.routeMap = {}
this.data = _Vue.observable({
current: '/'
})
}
createRouterMap() {
this.options.routes.forEach(route => this.routeMap[route.path] = route.component)
}
initComponents(Vue) {
// 創建RouterLink組件
Vue.component('router-link', {
props: {
'to': { type: String }
},
render(h) {
return h('a', {
attrs: { href: this.to },
on: { click: this.clickHandle }
}, [this.$slots.default])
},
methods: {
clickHandle(e) {
history.pushState({}, "", this.to)
// 把點擊的鏈接地址 更新到 current 上
this.$router.data.current = this.to
e.preventDefault()
}
}
// template: `<a :href="to"><slot></slot></a>`
})
let self = this
Vue.component('router-view', {
render(h) {
// routeMap以key value形式記錄了path和component
// data.current 記錄了當前頁面的path
return h(self.routeMap[self.data.current])
}
})
}
init() {
this.createRouterMap()
this.initComponents(_Vue)
this.initEvent()
}
initEvent() {
//
window.addEventListener("popstate", () => {
this.data.current = window.location.pathname
})
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/280343.html
標籤:其他
