乐趣区

关于javascript:大前端进阶Vuerouter原理

vue-router 是 vue 我的项目的重要组成部分,用于构建单页利用。单页利用是基于路由和组件的,路由用于设定拜访门路,并将门路和组件映射起来。路由的实质就是建设 url 和页面之间的映射关系。

hash 模式

hash 模式是 vue-router 的默认模式。hash 指的是 url 描点,当描点发生变化的时候,浏览器只会批改拜访历史记录,不会拜访服务器从新获取页面。因而能够监听描点值的变动,依据描点值渲染指定 dom。

实现原理

  • 扭转描点

能够通过 location.hash = "/hashpath" 的形式批改浏览器的 hash 值。

  • 监听描点变动

能够通过监听 hashchange 事件监听 hash 值的变动。

window.addEventListener('hashchange', () => {const hash = window.location.hash.substr(1)
   // 依据 hash 值渲染不同的 dom
})

history 模式

hash 模式下,url 可能为以下模式:

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

下面的 url 中既有 #又有?,会让 url 看上去很奇怪,因而,能够应用 history 模式,在此模式下,url 会如上面所示:

http://localhost:8080/book/1

实现原理

  • 扭转 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
退出移动版