大前端進階-Vue-router原理

vue-router是vue項目的重要組成部分,用於構建單頁應用。單頁應用是基於路由和組件的,路由用於設定訪問路徑,並將路徑和組件映射起來。路由的本質就是創建url和頁面之間的映射關係。html

hash模式

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
})

history模式

hash模式下,url可能爲如下形式:web

http://localhost:8080/index.html#/book?bookid=1vue-router

上面的url中既有#又有?,會讓url看上去很奇怪,所以,能夠使用history模式,在此模式下,url會以下面所示:express

http://localhost:8080/book/1api

實現原理

  • 改變url

H5的history對象提供了pushState和replaceState兩個方法,當調用這兩個方法的時候,url會發生變化,瀏覽器訪問歷史也會發生變化,可是瀏覽器不會向後臺發送請求。瀏覽器

// 第一個參數:data對象,在監聽變化的事件中可以獲取到
// 第二個參數:title標題
// 第三個參數:跳轉地址
history.pushState({}, "", '/a')
  • 監聽url變化

能夠經過監聽popstate事件監聽history變化,也就是點擊瀏覽器的前進或者後退功能時觸發。服務器

window.addEventListener("popstate", () => {
    const path = window.location.pathname
    // 根據path不一樣可渲染不一樣的dom
})

服務端支持

當使用hash模式的時候,若是手動刷新瀏覽器,頁面也可以正常顯示。可是在history模式下,刷新瀏覽器就會出現問題。

如訪問http://localhost:8080/book/1時,服務端會查找是否有相應的html可以匹配此路徑,在單頁應用下,服務端只有一個index.html,因此此時匹配不到,會提示404。針對這個問題,須要服務端進行history模式支持。

node服務

在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服務

在nginx服務中,能夠以下方式修改配置文件,添加history模式支持:

location / {
    root html;
    index index.html index.htm;
    #新添加內容
    #嘗試讀取$uri(當前請求的路徑),若是讀取不到讀取$uri/這個文     件夾下的首頁
    #若是都獲取不到返回根目錄中的 index.html
    try_files $uri $uri/ /index.html;
}

實現自定義VueRouter

VueRouter核心是,經過Vue.use註冊插件,在插件的install方法中獲取用戶配置的router對象。當瀏覽器地址發生變化的時候,根據router對象匹配相應路由,獲取組件,並將組件渲染到視圖上。

主要有三個重要點:

  • 如何在install方法中獲取vue實例上的router屬性。

能夠利用Vue.mixin混入聲明周期函數beforeCreate,在beforeCreate函數中能夠獲取到Vue實例上的屬性並賦值到Vue原型鏈上。

_Vue.mixin({
   beforeCreate () {
      if (this.$options.router) {
        _Vue.prototype.$router = this.$options.router
      }
   }
})
  • 如何觸發更新

hash模式下:

  1. 經過location.hash修改hash值,觸發更新。
  2. 經過監聽hashchange事件監聽瀏覽器前進或者後退,觸發更新。

history模式下:

  1. 經過history.pushState修改瀏覽器地址,觸發更新。
  2. 經過監聽popstate事件監聽瀏覽器前進或者後退,觸發更新。
  • 如何渲染router-view組件
  1. 經過Vue.observable在router實例上建立一個保存當前路由的監控對象current。
  2. 當瀏覽器地址變化的時候,修改監控對象current。
  3. 在router-view組件中監聽監控對象current的變化,當current變化後,獲取用戶註冊的相應component,並利用h函數將component渲染成vnodes,進而更新頁面視圖。

完整版

// 存儲全局使用的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
相關文章
相關標籤/搜索