初学vue(1)-实现一个Vue Router

212 阅读3分钟

初学vue(1)-实现一个Vue Router

概述

Vue Router是Vue.js官方的路由管理器。它和Vue.js的核心深度集成,让构建单页面应用变的易如反掌。

安装: vue add router

基本步骤:

  1. 使用vue-router插件 router.js
import Vue from 'vue';

import Router from 'vue-router';

Vue.use(Router);

2.创建Router实例 router.js

export default new Router({...})

3.在根组件上添加实例 main.js

import router from './router';

new Vue({

    router

}).$mount('#app')

4.添加路由视图 App.vue

<router-view></router-view>

问题点

  1. 为什么使用插件的时候要使用use?Router在use中到底做了些什么?
  2. 为什么要把router实例要挂在到根实例这里?作用是什么?
  3. 为什么router-view和router-link可以直接使用?
  4. router-link又是怎么工作的?

思路

Vue-Router在单页面应用程序中,url发生变化时,不刷新就能显示对应的视图

  • spa页面不能刷新的方法

    • Hash #/home :哈希模式在url后面加#,类似于超链接加#就不会刷新页面
    • History api /home: History其实就是H5中的pushState,其核心思想就是让浏览器的url发生变化,但是又不会发生http请求
  • 根据url显示对应的视图

    • router-view:路由出口,用来存放页面内容容器
    • 数据响应式: current变量持有url地址,一旦变化,动态重新执行renter

好了,我们就要来实现一个vue-router的插件,为什么要实现一个插件呢?因为我们在使用router的时候会使用Vue.use()的方法,Vue.use的内部实际上调用的是插件的install()方法。

所以我们来想一下实现这个插件要做哪些事情:

  • 实现一个VueRouter的类

    • 处理路由选项
    • 监控url变化,通过hashchange来响应这个变化
  • 实现一个install方法

    • 把router的实例$router注册一下
    • 两个全局组件 router-view、router-link

实现步骤

挂载$router

好的,接下来创建文件,定义插件类和方法

然后接下来我们要干什么?注册$router对不对。然后此时衍生出来了一个问题,我们为什么要在main.js中引入router实例然后挂在new Vue上?实例挂到这里,其实就是为了插件安装时可以注册当前的实例;

好了,言归正传,我们平时在使用的时候都是this.$router,那么我们怎么去挂载呢?当然是挂载到Vue原型上了

那么问题来了,这个router实例怎么取?在router.js中我们先使用了Vue.use(),在这时候,Vue的实例不存在,那就更别提router实例了,此时应该怎么办?好了,别急~只要思想不滑坡办法总比困难多。全局混入你值得拥有!mixin(),将Router实例挂在过程延迟到Vue实例构建之后;

接下来我们在mixin中定义一个beforeCreate的方法,注意啊!!!问题来了,在beforeCreate的this指向哪???

没错就是组件的实例

接下来大家考虑一下,这样写是否可行?理论上来说是可行的,但是这是一个全局混入所有组件实例都会执行Vue.prototype.router=this.router = this.options.router。嗯~~~这样看来有点不靠谱啊这。因为router实例的只有一个地方,那就是跟实例所以从安全角度,我们要进行一次判断,判断是否存在this.$options.router

好了,此时此刻我们的第一步工作已经做完了;不急,接下来我们看一下页面进展如何?

ca~居然有两个小错,当然这也并不奇怪,我们还没有实现那两个全局组件呢,好的接下来我们来实现这两个全局组件

注册全局组件

router-link

接下来我们先来实现一个router-link吧,期望实现效果是,那就简单了直接渲染一个template

那么这样写行不行?答案是肯定不行了,浏览器会报错,错误提示是执行当前Vue的版本是一个“runtime-only”的版本,没有办法把这个字符串模版进行编译,

我们大部门的执行环境是vueCLI创建的,基于webpack的运行时版本,runtime版本是不包含编辑器的,所以字符串模版能解决吗?肯定是不能解决了,那这种方式你就死了心吧。我们只有一种方式可以写,那就是自己写个render函数

ok,router-link这部分实现了,结下来开始搞router-view

router-view

思考一下,我们如何渲染一个组件出来?

通过url从路由表中取到这个组件,但是怎么获取到路由表的信息?监听hashchange事件对不对,但是hashchange在肯定就不能在install方法里定义了,我们接下来先去VueRouter把我们所需要的信息持有一下:

1.存储一下将来要接受的一些选项

2.定义一个current变量用来保存当前路由的信息

3.监听一下hashchange的变化

接下来我们开始去router-view去尝试获取一下router实例。我们之前在mixin方法中挂载过$router,router-view又集成于Vue,那么就好了,我们直接用this.$router,然后再通过之前在YQVueRouter定义$options,就可以取到路由表了

好的,很爽~接下来我们开始遍历,查找一下和当前路由一样元素,然后就可以获取到路由表的组件信息了

好了我们的router-view组件已经实现好了,但是在点击页面的时候发现路由已经改变,但是页面没有重新渲染。这是为什么呢?

current变量响应式

查找一番,发现还有一步没有做呢,current变量不是响应式的,导致下面的render函数没有重新执行;好的接下来我们吧current变成响应式的;现在我们来思考一个问题,响应式数据我们天天用,但是我们应该怎么造一个响应式的数据呢?(proxy先不考虑啊,咱们现在用的是vue2.0,先不考虑3.0)

object.defineproperty?但是defineproperty只能设置属性的拦截,并没有做依赖收集的过程, 不是说defineproperty设置了这个数据就变成响应式了,我们只是拦截了属性,这个属性发生了改变我们应该怎么办?没办法做下一步啊,是不是?

$set? $set更不行,咱们平时在使用$set的时候,this.$set(obj, 'key', 'val'),但是$set要求obj本身就是一个响应式对象,我们这里并没有响应式对象

其实这里呢Vue有一个隐藏的api,平时开发时,大家都很少用到Vue.util.defineReactive(),这是官方给我们提供的,可以给指定的对象设置响应式数据

发现Vue is not defined ?原来我们没有定义,这个大家看了是不是就会说,直接引用一个Vue不就好了吗?这样咋说呢,不是说不可以,但是如果你有天写个第三方的库,难道还要把Vue打包进去吗?

其实这里可以先用let全局保存一个Vue的构造函数,然后在install的时候赋值一下

现在再去看我们的页面,只能用丝滑二字来形容了

好了一个简版的自定义VueRouter就写完了。

码字不易,如有不足希望各位看官提出宝贵的建议,小弟先行谢过了;觉得写的不错就给兄弟点个赞,您的点赞是我写下去的动力,我会继续努力的。