Vue Router的手写实现方法实现


Posted in Javascript onMarch 02, 2020

为什么需要前端路由

在前后端分离的现在,大部分应用的展示方式都变成了 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手写内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
jQuery 追加元素的方法如append、prepend、before
Jan 16 Javascript
Javascript 实现复制(Copy)动作方法大全
Jun 20 Javascript
一个判断抢购时间是否到达的简单的js函数
Jun 23 Javascript
js实现数组冒泡排序、快速排序原理
Mar 08 Javascript
简单实现js页面切换功能
Jan 10 Javascript
Vue.js一个文件对应一个组件实践
Oct 27 Javascript
微信小程序 页面传参实例详解
Nov 16 Javascript
前端常见跨域解决方案(全)
Sep 19 Javascript
原生JS实现网页手机音乐播放器 歌词同步播放的示例
Feb 02 Javascript
node+koa2+mysql+bootstrap搭建一个前端论坛
May 06 Javascript
微信小程序实现留言功能
Oct 31 Javascript
vue实现下载文件流完整前后端代码
Nov 17 Vue.js
ES6中Set和Map用法实例详解
Mar 02 #Javascript
Vue父组件向子组件传值以及data和props的区别详解
Mar 02 #Javascript
js中addEventListener()与removeEventListener()用法案例分析
Mar 02 #Javascript
js构造函数constructor和原型prototype原理与用法实例分析
Mar 02 #Javascript
原生js实现日历效果
Mar 02 #Javascript
js中火星坐标、百度坐标、WGS84坐标转换实现方法示例
Mar 02 #Javascript
详解Vue中的Props与Data细微差别
Mar 02 #Javascript
You might like
PHP读取汉字的点阵数据
2015/06/22 PHP
JQuery 表格操作(交替显示、拖动表格行、选择行等)
2009/07/29 Javascript
Jquery Ajax学习实例4 向WebService发出请求,返回实体对象的异步调用
2010/03/16 Javascript
让元素在网页中可拖动示例代码
2013/08/13 Javascript
jQuery.extend()、jQuery.fn.extend()扩展方法示例详解
2014/05/08 Javascript
js 中将多个逗号替换为一个逗号的代码
2014/06/07 Javascript
javascript setinterval 的正确语法如何书写
2014/06/17 Javascript
原生javascript实现读写CSS样式的方法详解
2017/02/20 Javascript
vue实现百度搜索下拉提示功能实例
2017/06/14 Javascript
vue.js国际化 vue-i18n插件的使用详解
2017/07/07 Javascript
jQuery滚动条美化插件nicescroll简单用法示例
2018/04/18 jQuery
python3访问sina首页中文的处理方法
2014/02/24 Python
python通过pip更新所有已安装的包实现方法
2017/05/19 Python
基于Python和Scikit-Learn的机器学习探索
2017/10/16 Python
使用Python的package机制如何简化utils包设计详解
2017/12/11 Python
微信跳一跳辅助python代码实现
2018/01/05 Python
python引入不同文件夹下的自定义模块方法
2018/10/27 Python
python实现广度优先搜索过程解析
2019/10/19 Python
Java文件与类动手动脑实例详解
2019/11/10 Python
如何在Python对Excel进行读取
2020/06/04 Python
利用CSS3的线性渐变linear-gradient制作边框的示例
2016/06/02 HTML / CSS
中东奢侈品购物网站:Ounass
2020/09/02 全球购物
PatPat香港:婴童服饰和亲子全家装在线购物
2020/09/27 全球购物
初始化了一个没有run()方法的线程类,是否会出错?
2014/03/27 面试题
简历中自我评价范文3则
2013/12/14 职场文书
小学开学典礼主持词
2014/03/19 职场文书
保护环境建议书300字
2014/05/13 职场文书
推普周国旗下讲话稿
2014/09/21 职场文书
2014入党积极分子批评与自我批评思想报告
2014/10/06 职场文书
法定代表人授权委托书格式
2014/10/14 职场文书
2014年小学图书室工作总结
2014/12/09 职场文书
办公室主任岗位职责范本
2015/03/31 职场文书
演讲开场白台词大全
2015/05/29 职场文书
安全教育主题班会教案
2015/08/12 职场文书
Dubbo+zookeeper搭配分布式服务的过程详解
2022/04/03 Java/Android
公历12个月名称的由来
2022/04/12 杂记