Vue-Router实现原理

308 阅读3分钟

概念

通过改变 URL,在不重新请求页面的情况下,更新页面视图。

前后端路由的区别

后端路由: 输入url->向服务器发送请求->服务器解析请求路径->拿到对应页面->返回给前端

前端路由: 输入url->js解析地址->找到对应的页面->执行页面生产的js->页面呈现

Vue工作原理

url改变
⬇️
触发监听事件
⬇️
改变vuerouter中的current变量
⬇️
vue监视current的改变
⬇️
获取到新的组件
⬇️
Render新组件

hash与history

更新视图但不重新请求页面,是前端路由原理的核心之一,目前在浏览器环境中这一功能的实现主要有两种方式

    1.Hash --- 利用 URL 中的hash("#");

    2.利用 History interface 在HTML5中新增的方法。

  在 Vue 中,它是通过 mode 这一参数控制路由的实现模式

const router=new VueRouter({
    mode:'history',
    routes:[...]
})

两种模式的区别

  1. hash - 即地址栏URL中携带 # 符号(此hash不是密码学里的散列运算)
    比如这个URL: http://www.abc.com/#/hello hash的值为#/hello.它的特点在于:hash虽然出现在URL中,但不会被包括在HTTP请求中,对后端完全没有影响,因此改变hash不会重新加载页面。

  2. history - 利用了HTML5 History Interface中新增的pushState()和replaceState()方法。(需要特定浏览器支持) 这两个方法应用于浏览器的历史记录栈,在当前已有的back、forward、go的基础上,它们提供了对历史记录进行修改的功能。只是当它们执行修改时,虽然改变了当前的URL,但浏览器不会立即向后端发送请求。

因此hash模式和histoury模式都是属于浏览器自身的特性,Vue-Router只是利用了这两个特性(通过调用浏览器提供的接口)来实现前端路由

使用场景
调用history.pushState()相比于直接修改hash ,存在以下优势:

  1. pushState()设置的新URL可以是与当前URL同源的任意URL;而hash只可修改#后面的部分,因此只能设置与当前URL同文档的URL;
  2. pushState()设置的新URL可以与当前URL一模一样,这样也会把记录添加到栈中;而hash设置的新值必须与原来不一样才会触发动作将记录添加到栈中;
  3. pushState()通过stateObject参数可以添加任意类型的数据到记录中;而hash只可添加短字符串;
  4. pushState()可额外设置title属性供后续使用。

当然history也不是样样都好。SPA虽然在浏览器里游刃有余,但真要通过URL向后端发起HTTP请求时,两者的差异就来了。尤其在用户手动输入URL后回车,或者刷新(重启)浏览器的时候。

  1. hash 模式下,仅hash符号之前的内容会被包含在请求中,如 www.abc.com 因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回404错误。
  2. history模式下,前端的URL必须和实际向后端发起请求的URL一致。如 htttp://www.abc.com/book/id 如果后端缺少对/book/id 的路由处理,将返回404错误

Vue-Router

  1. vue.use

    • 执行里面的方法
    • 如果方法有install属性,并且install是个方法,仅执行install
    • install方法的第一个参数是vue的构造函数
  2. vueRouter实例

  3. main.js

  4. 两个全局组件

代码

let Vue
class VueRouter{
  constructor(options){
    let initial = '/'
    Vue.util.defineReactive(this,'current',initial)//实现响应式
    this.current = '/'//当前路由地址
    this.routes = options.routes//路由表
    this.mode = options.mode || 'hash'
    this.init()//监听路由改变
    // console.log(Vue.util.defineReactive);//对objectdefineproperty进行了封装
  }
  init(){
    if(this.mode === 'hash'){
      //第一次加载
      window.addEventListener('load',()=>{
        this.current = location.hash.slice(1)
      })
      //监听hash事件
      window.addEventListener('hashchange',()=>{
        this.current = location.hash.slice(1)
      })
    }
  }
}


VueRouter.install = function(_Vue){
  Vue = _Vue
  //给调用的组件添加router属性
  Vue.mixin({//混入全局
    beforeCreate() {
      if(this.$options.router){
        Vue.prototype.$router = this.$options.router
      }
    },
  })
  
  Vue.component('router-link',{
    props:{
      to:{
        type:String,
        require:true
      }
    },
    render(h) {
      return h('a',{
        attrs:{
          href:'#'+this.to
        }
      },this.$slots.default)
    },
  })
  Vue.component('router-view',{
    render(h) {
      console.log(this.$router);
      let current = this.$router.current
      let routes = this.$router.routes
      let com = routes.find(item=> current === item.path)
      
      return h(com.component)
    },
  })
}


export default VueRouter