VueRouter的實(shí)現(xiàn)原理——封裝簡(jiǎn)易功能的VueRouter

2021-4-27    前端達(dá)人

Hash模式和History模式的區(qū)別

不管哪種模式,前端路由都是客戶端路由的實(shí)現(xiàn)方式,也就是當(dāng)路徑發(fā)生變化時(shí),不會(huì)向服務(wù)器發(fā)送請(qǐng)求,是利用js監(jiān)視路徑的變化。然后根據(jù)不同的地址渲染不同的內(nèi)容,如果需要服務(wù)器內(nèi)容,會(huì)發(fā)送Ajax請(qǐng)求來獲取。

表現(xiàn)形式的區(qū)別

  • hash 模式
    https://music.163.com/#/discover/toplist 地址中會(huì)存在 # 號(hào)
  • history 模式
    https://music.163.com/discover/toplist 地址中沒有# 類似于普通的地址,但是需要服務(wù)端配置支持

原理的區(qū)別

  • hash 模式是基于錨點(diǎn), 以及onhashchange 事件
  • history 模式是基于 HTML5 中的 History API
    • history.pushState() IE10 以后才支持
    • history.replaceState() \

History 模式的使用

  • History 需要服務(wù)器的支持
  • 單頁應(yīng)用中,如果刷新頁面,會(huì)向服務(wù)器發(fā)起請(qǐng)求,而服務(wù)器不存在這樣的地址就會(huì)返回找不到該頁面從而出現(xiàn)404
  • 在服務(wù)端應(yīng)該除了靜態(tài)資源外都返回單頁應(yīng)用的 index.html

node 環(huán)境下支持 history

在 node 環(huán)境下,啟用對(duì)history模式的支持可以通過 connect-history-api-fallback 這個(gè)中間件來完成

// 導(dǎo)入處理 history 模式的模塊 const history = require('connect-history-api-fallback') // 導(dǎo)入 express const express = require('express') const app = express() // 注冊(cè)處理 history 模式的中間件 app.use(history()) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Nginx 下支持 history

  • 從官網(wǎng)下載 nginx 的壓縮包
  • 把壓縮包解壓到 c 盤根目錄,c:\nginx-1.18.0 文件夾
  • 修改 conf\nginx.conf 文件

運(yùn)行nginx服務(wù)器基本指令

啟動(dòng)
start nginx
重啟
nginx -s reload
停止
nginx -s stop

  • 修改 conf\nginx.conf 文件
location / { root  html; index  index.html index.htm; #新添加內(nèi)容
     #嘗試讀取$uri(當(dāng)前請(qǐng)求的路徑),如果讀取不到讀取$uri/這個(gè)文件夾下的首頁
     #如果都獲取不到返回根目錄中的 index.html
     try_files $uri $uri/ /index.html; } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

VueRouter 兩種模式的實(shí)現(xiàn)原理

Hash 模式

  • URL 中 # 后面的內(nèi)容作為路徑地址
  • 監(jiān)聽 hashchange 事件
  • 根據(jù)當(dāng)前路由地址找到對(duì)應(yīng)組件重新渲染

History 模式

  • 通過 history.pushState() 方法改變地址欄
  • 監(jiān)聽 popstate 事件
  • 根據(jù)當(dāng)前路由地址找到對(duì)應(yīng)組件重新渲染

實(shí)現(xiàn)思路

在這里插入圖片描述
從上圖,可以大致了解一下 VueRouter 這個(gè)類中的結(jié)構(gòu):
上半部分是屬性,下半部分是方法,其中+ 是實(shí)例方法,- 是靜態(tài)方法。
install 是用來實(shí)現(xiàn)Vue.use 插件機(jī)制的方法。

VueRouter-install 方法實(shí)現(xiàn)

要實(shí)現(xiàn)install方法,首先先分析一下該方法要做的事情:

  1. 判斷當(dāng)前插件是否已經(jīng)被安裝
  2. 把Vue構(gòu)造函數(shù)記錄到全局變量
  3. 把創(chuàng)建Vue實(shí)例時(shí)候傳入的router對(duì)象注入到所有的Vue實(shí)例上
 let _Vue; export default class VueRouter { static install(Vue) { // 1. 判斷當(dāng)前插件是否已經(jīng)被安裝 if(VueRouter.install.installed) return VueRouter.install.installed = true // 2. 把Vue構(gòu)造函數(shù)記錄到全局變量 _Vue = Vue // 3. 把創(chuàng)建Vue實(shí)例時(shí)候傳入的router對(duì)象注入到所有的Vue實(shí)例上 // 利用混入讓所有的vue實(shí)例加載router _Vue.mixin({ beforeCreate(){ // this.$options.name用來獲取vue實(shí)例 data以外的屬性 // new Vue( { router } ) if(this.$options.router) { _Vue.prototype.$router = this.$options.router } } }) } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

添加 VueRouter 的constructor

VueRouter 的構(gòu)造函數(shù)要初始化三個(gè)屬性,分別是: options、data、routeMap。

  • options 是路由的構(gòu)造配置對(duì)象
  • data 應(yīng)該是一個(gè)響應(yīng)式的對(duì)象,其中有一個(gè)屬性 current 用來記錄當(dāng)前我們的路由地址,這里我們?cè)撊绾尾拍軇?chuàng)建一個(gè)響應(yīng)式的對(duì)象呢?可以使用Vue的observable方法
  • routeMap 中記錄了 options里的rules,rules解析出來 會(huì)以鍵值對(duì)的形式存在 routeMap中 ,key 就是路由地址,value 就是路由組件
constructor(options){ this.options = options this.data = _Vue.observable({ current:'/' }) this.routeMap = {} } 
  • 1
  • 2
  • 3
  • 4
  • 5

createRouterMap

接下來我們來實(shí)現(xiàn)VueRouter類中 createRouterMap 這個(gè)方法,它的作用就是把 options 中rules 路由規(guī)則解析出來以鍵值對(duì)的形式存儲(chǔ)在routeMap上。

 createRouterMap() { this.options.rules.forEach(route => this.routeMap[route.path] = route.component) } 
  • 1
  • 2
  • 3

initComponents

下一步,來創(chuàng)建initComponents 方法,這個(gè)方法里我們要?jiǎng)?chuàng)建兩個(gè)組件。分別是:RouterLink 和 RouterView

創(chuàng)建RouterLink 組件

let _Vue; export default class VueRouter { static install(Vue) { // 1. 判斷當(dāng)前插件是否已經(jīng)被安裝 if (VueRouter.install.installed) return VueRouter.install.installed = true // 2. 把Vue構(gòu)造函數(shù)記錄到全局變量 _Vue = Vue // 3. 把創(chuàng)建Vue實(shí)例時(shí)候傳入的router對(duì)象注入到所有的Vue實(shí)例上 // 利用混入讓所有的vue實(shí)例加載router _Vue.mixin({ beforeCreate() { // this.$options.name用來獲取vue實(shí)例 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) { // 創(chuàng)建RouterLink組件 Vue.component('router-link', { props: { 'to': { type: String } }, template: `<a :href="to"><slot></slot></a>` }) } init() { this.createRouterMap() this.initComponents(_Vue) } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

用自己的VueRouter 替換掉官方的運(yùn)行后,發(fā)現(xiàn)報(bào)錯(cuò)
在這里插入圖片描述
報(bào)錯(cuò)的意思是,運(yùn)行時(shí)版本的Vue 不支持 tempalte 模板,需要打包的時(shí)候提前編譯。
如果要讓我們的template被支持可以使用完整版的Vue,完整包包含運(yùn)行時(shí)和編譯器,體積比運(yùn)行時(shí)版本大10k左右,程序運(yùn)行的時(shí)候把模板轉(zhuǎn)換成render函數(shù)
@vue/cli 自動(dòng)安裝的就是 運(yùn)行時(shí)版本

報(bào)錯(cuò)的解決

第一種方案——引入完整版Vue,可以在vue.config.js中 加入配置

module.exports = { runtimeCompiler: true } 
  • 1
  • 2
  • 3

第二種方案——使用render函數(shù)替換掉tempalte

 render(h) { return h('a', { attrs: { href: this.to } }, [this.$slots.default]) } // template: `<a :href="to"><slot></slot></a>` 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

創(chuàng)建RouterView組件

 // 記錄一下this let self = this Vue.component('router-view',{ render(h){ // routeMap以key value形式記錄了path和component // data.current 記錄了當(dāng)前頁面的path return h(self.routeMap[self.data.current]) } }) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在routerlink中添加點(diǎn)擊事件,修改地址

為了能夠讓鏈接成功完成跳轉(zhuǎn)展示組件,我們需要對(duì)routerlink中的a標(biāo)簽添加點(diǎn)擊事件

并且要在點(diǎn)擊的時(shí)候,把最新的path更新到router實(shí)例的current上.

我們借助于history的pushState方法 該方法會(huì)修改瀏覽器地址欄中的地址,但不會(huì)向服務(wù)器發(fā)起請(qǐng)求,并且還可以將新地址記錄在歷史中

 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) // 把點(diǎn)擊的鏈接地址 更新到 current 上 this.$router.data.current = this.to
                    e.preventDefault() } } // template: `<a :href="to"><slot></slot></a>` }) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

initEvent

現(xiàn)在功能基本上已經(jīng)差不多了,但是還存在一個(gè)小問題,就是當(dāng)我們點(diǎn)擊瀏覽器的前進(jìn)或者后退按鈕的時(shí)候,組件不能實(shí)現(xiàn)切換展示,主要思路就是通過添加popstate監(jiān)聽地址變化,下面我們來完善該功能

 initEvent(){ // window.addEventListener("popstate",()=>{ this.data.current = window.location.pathname }) } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

完整代碼

let _Vue; export default class VueRouter { static install(Vue) { // 1. 判斷當(dāng)前插件是否已經(jīng)被安裝 if (VueRouter.install.installed) return VueRouter.install.installed = true // 2. 把Vue構(gòu)造函數(shù)記錄到全局變量 _Vue = Vue // 3. 把創(chuàng)建Vue實(shí)例時(shí)候傳入的router對(duì)象注入到所有的Vue實(shí)例上 // 利用混入讓所有的vue實(shí)例加載router _Vue.mixin({ beforeCreate() { // this.$options.name用來獲取vue實(shí)例 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) { // 創(chuàng)建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) // 把點(diǎn)擊的鏈接地址 更新到 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 記錄了當(dāng)前頁面的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 }) } }

轉(zhuǎn)自:csdn 作者:Holyforsaken_FHC


藍(lán)藍(lán)設(shè)計(jì)www.yvirxh.cn )是一家專注而深入的界面設(shè)計(jì)公司,為期望卓越的國(guó)內(nèi)外企業(yè)提供卓越的UI界面設(shè)計(jì)、BS界面設(shè)計(jì) 、 cs界面設(shè)計(jì) 、 ipad界面設(shè)計(jì) 、 包裝設(shè)計(jì) 、 圖標(biāo)定制 、 用戶體驗(yàn) 、交互設(shè)計(jì)、 網(wǎng)站建設(shè) 、平面設(shè)計(jì)服務(wù)

日歷

鏈接

個(gè)人資料

存檔