欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Vue Router的手写实现方法实现

程序员文章站 2022-05-17 08:53:13
为什么需要前端路由在前后端分离的现在,大部分应用的展示方式都变成了 spa(单页面应用 single page application)的模式。为什么会选择 spa 呢?原因在于: 用户的所有操作都...

为什么需要前端路由

在前后端分离的现在,大部分应用的展示方式都变成了 spa(单页面应用 single page application)的模式。为什么会选择 spa 呢?原因在于:

  • 用户的所有操作都在同一个页面下进行,不进行页面的跳转。用户体验好。
  • 对比多页面,单页面不需要多次向服务器请求加载页面(只请求一次.html文件),只需要向服务器请求数据(多亏了 ajax)。因此,浏览器不需要渲染整个页面。用户体验好。

归根结底,还是因为 spa 能够提供更好的用户体验。

为了更好地实现 spa,前端路由是必不可少的。假设一个场景:用户在 spa 页面的某个状态下,点击了强制刷新按钮。如果没有前端路由记住当前状态,那么用户点击该按钮之后,就会返回到最开始的页面状态。这不是用户想要的。

当然,需要前端路由另一个点在于:我们可以更好地进行 spa 页面的管理。通过将组件与路由发生配对关联,依据路由的层级关系,可为 spa 内部的组件划分与管理提供一个依据参考。

hash 路由模式 与 history 路由模式

这是两种常见的前端路由模式。

hash 路由模式

hash 模式使用了浏览器 url 后缀中的#xxx部分来实现前端路由。默认情况下,url后缀中的#xxx hash 部分是用来做网页的锚点功能的,现在前端路由看上了这个点,并对其加以利用。
比如这个 url:http://www.abc.com/#/hello,hash 的值为 #/hello。

为什么会看上浏览器url后缀中的 hash 部分呢?原因也简单:

  • 浏览器url后缀中的 hash 改变了,不会触发请求,对服务器完全没有影响。它的改变不会重新加载浏览器页面。
  • 更关键的一点是,因为hash发生变化的url都会被浏览器记录下来,从而你会发现浏览器的前进后退都可以用了,页面的状态与浏览器的url就发生了挂钩。

hash模式背后的原理是onhashchange事件,可以在window对象上监听这个事件。

history 路由模式

随着 html5 中 history api 的到来,前端路由开始进化了。hashchange 只能改变 # 后面的代码片段,history api (pushstate、replacestate、go、back、forward) 则给了前端完全的*。简单讲,它的功能更为强大了:分为两大部分,切换和修改。

路由切换

参考mdn,切换历史状态包括 back、forward、go 三个方法,对应浏览器的前进,后退,跳转操作。

history.go(-2);//后退两次
history.go(2);//前进两次
history.back(); //后退
hsitory.forward(); //前进

路由修改

修改历史状态包括了pushstate,replacestate两个方法:

/**
 ** 参数含义
 ** state: 需要保存的数据,这个数据在触发popstate事件时,可以在event.state里获取
 ** title:标题,基本没用,一般传 null
 ** url:设定新的历史记录的 url
 */ 
window.history.pushstate(state, title, url) 

//假设当前的url是:https://www.abc.com/a/
//例子1
history.pushstate(null, null, './cc/') //此时的url为https://www.abc.com/a/cc/
//例子2
history.pushstate(null, null, '/bb/') //此时的url为https://www.abc.com/bb/

同样的,history 模式可以监听到对应的事件:

window.addeventlistener("popstate", function() {
// 监听浏览器前进后退事件,pushstate 与 replacestate 方法不会触发 
});

history 模式的注意点

和 hash 模式相比,history 模式存在着更多的选择。但是也有一些自身的注意点:在用户点击强制刷新的时候,history 模式会向服务器发送请求。

为了解决这个问题,需要服务器做对应的处理。服务器可以针对不同的url进行处理,当然,也可以简单处理:只要是未匹配到的url请求,一律返回同一个 index.html 页面。

vue router 做了什么?

vue router 作为 vue 生态系统中非常重要的一个成员,它实现了 vue 应用的路由管理。可以说,vue router 是专门为 vue 量身定制的路由管理器,功能点非常多。它的内部实现是与 vue 自身是有强耦合关系的(vue router 内部利用了 vue 的数据响应式)。
我们来看一个典型的 vue router 配置:

import vue from "vue";
import app from "./vue/app.vue";
import vuerouter from 'vue-router';

//以插件的形式,使用vuerouter
vue.use(vuerouter);

//路由配置信息,可以从外部文件引入,在此直接写是为了方便演示
const foo = { template: '<div>foo</div>' }
const bar = { template: '<div>bar</div>' }
const routes = [
 { path: '/', component: foo },
 { path: '/bar', component: bar }
]

//初始化并与 vue 实例关联
const router = new vuerouter({routes});
new vue({
 router,
 render: h => h(app),
}).$mount("#root");

可看出,vuerouter 是作为插件的形式引入到 vue 系统内部的。而将具体的 router 信息嵌入到每个 vue 实例中,则是作为 vue 的构造函数参数传入。

同时来看看如何使用它:

//routerexample.vue
<template>
  <div>
    <h1 @click="goback">app test</h1>
    <router-link to="/">foo</router-link>
    <router-link to="/bar">bar</router-link>

    <router-view></router-view>
  </div>
</template>

<script>
export default {
 methods: {
  goback() {
   console.log(this.$router); 
   window.history.length > 1 ? this.$router.go(-1) : this.$router.push('/')
  }
 }
}
</script>

<style lang="less" scoped>

</style>

上面的代码中,我们可以直接使用router-link和router-view这两个组件。它们是随着 vue router 一起引入的,作为全局组件使用。

这就是一个最简单的 vue router 的使用方式。我们下面就来看看,该如何自己实现上面的简单功能,做一个自己的 vue router。

一个简单的 vue router 实现

看了上面的这个过程,最简单的 vue router 应该包括以下实现步骤:

实现 vue 规定的插件的写法,将我们自己的vue router 作为插件引入 vue 系统中。

  • router功能一:解析传入的routes选项,以备调用
  • router功能二:监控url变化(两种路由方式:history、hash)

实现两个全局组件:router-link和router-view

看看自定义的 vue router 的实现:

//fvuerouter.js

 let vue; //保存 vue 构造函数的引用,与 vue 深度绑定

 class fvuerouter {
  constructor(options){
    this.$options = options;
    //保存路由的路径与路由组件的对应关系
    this.routermap = {};

    //当前的url必须是响应式的,使用一个新的 vue 实例来实现响应式功能
    this.app = new vue({
      data: {current : "/"}
    })
  }

  init(){
    //监听路由事件
    this.bindevents();
    //解析传入的routes
    this.createroutermap();
    //全局组件的声明
    this.initcomponent();
  }

  bindevents(){
    window.addeventlistener('hashchange', this.onhashchange.bind(this));
  }

  onhashchange(){
    this.app.current = window.location.hash.slice(1) || '/';
  }

  createroutermap(){
    this.$options.routes.foreach(route => {
      this.routermap[route.path] = route;
    })
  }

  initcomponent() {
    // 形式:<router-link to="/"> 转换目标=> <a href="#/" rel="external nofollow" >xxx</a>
    vue.component("router-link", {
     props: {
      to: string,
     },
     render(h) {
      // h(tag, data, children)
      return h('a', {
        attrs: {href: '#' + this.to}
      }, [this.$slots.default])
     },
    });
    // 获取path对应的component将它渲染出来
    vue.component("router-view", {
      render: (h) => {
        //此处的this 能够正确指向 fvouter内部,是因为箭头函数
        const component = this.routermap[this.app.current].component;
        return h(component)
      }
    })
   }
 }

 // 所有的插件都需要实现install 方法,传入参数是vue的构造函数
 fvuerouter.install = function(_vue){
  //将vue的构造函数保存起来
  vue = _vue;

  //实现一个混入操作的原因,插件的install阶段非常早,此时并没有vue实例
  //因此,使用mixin,延迟对应操作到vue实例构建的过程中来执行。
  vue.mixin({
    beforecreate(){
      //获取到router的实例,并将其挂载在原型上
      if(this.$options.router){
        //根组件beforecreate时只执行一次
        vue.prototype.$router = this.$options.router;

        this.init();
      }
    }
  })
 }

export default fvuerouter;

这里是最为简单的一种实现。有几个值得注意的点:

  • 如上代码,将最基本的一个vue router 的代码架子搭建起来了,能够运行。但细微处依然需要酌情考虑。
  • 关于插件的写法:自定义插件内部必须实现一个 install 方法,传入参数是vue的构造函数。
  • 使用了一个新的vue 实例,将 url 的 hash 变量进行数据响应化处理。
  • 关于渲染函数 render 的参数 h,它实际上是 createelement 函数。具体用法值得深究。代码中使用的是最为简单的处理方式。

结尾

在本文中,我们讲解了 前端路由常见的两种模式:hash 模式与 history 模式。同时,我们尝试自己实现了一个最为简单的 vue router。更多相关的 vue router 的细节,可以参考其官网。希望本文对你有用。

到此这篇关于vue router的手写实现方法实现的文章就介绍到这了,更多相关vue router手写内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!