撸一个简单的vue-router来剖析原理
理解
随着前端业务的发展,
我们一般在写一个较为大型的vue
项目时候,会使用到vue-router
,来根据指定的url
或者hash
来进行内容的分发,可以达到不像服务端发送请求,就完成页面内容的切换,能够减少像服务器发送的请求,让用户进行页面跳转时候能够更快,体验更好
疑问
在初学vue-router
的时候,一般人都会有一个印象,router-link
以及router-view
都是vue
原生自带的标签。但是这个印象是错误的,vue-router
本质上是一个vue
的插件,通过vue.use(vuerouter)
来使用这个插件。router-link
以及router-view
也是这个插件实现的自定义标签。
本文以开发插件的模式,撸一个vue-router
插件以加深对其原理的了解
url变化流程图解
也就是说,要实现一个简单的vue-router
,需要完成以下需求
具体操作
创建vue项目
vue create my-vue-router
由于只着重于vue-router
的内容,所以先使用原本的vue-router
这样只替换vue-router
源码文件即可
增加vue-router
vue add router
然后项目目录就变成了
my-vue-router |- node_modules |- public |- src |- assets |- components |- hellowworld.vue |- router |- index.js |- views |- about.vue |- home.vue |- app.vue |- main.js |- .gitinore |- babel.config.js |- package.json |- readme.md |- yarn.lock
在目录中,新建一个myrouter.js
的文件,来放置我们的源码
新建自己的myrouter文件
my-vue-router |- node_modules |- public |- src |- assets |- components |- hellowworld.vue |- router |- index.js + |- myrouter.js |- views |- about.vue |- home.vue |- app.vue |- main.js |- .gitinore |- babel.config.js |- package.json |- readme.md |- yarn.lock
切换引入文件为自己写的myrouter.js
此时,@/src/router/index.js
中的内容里,我们将导入的vue-router
替换为我们的myrouter.js
import vue from 'vue' - import vuerouter from 'vue-router' + import vuerouter from './myrouter' import home from '../views/home.vue' vue.use(vuerouter) const routes = [ { path: '/', name: 'home', component: home }, { path: '/about', name: 'about', // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import(/* webpackchunkname: "about" */ '../views/about.vue') } ] const router = new vuerouter({ mode: 'history', base: process.env.base_url, routes }) export default router
这里我们可以看到,代码执行的流程为
引入myrouter.js
->配置routes对象
->new vuerouter
->export default
导出
此处用到了 vue.use()
这个api
vue.use()
vue
中的插件,一个核心的api
就是vue.use()
安装 vue.js 插件。如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,它会被作为install 方法。install 方法调用时,会将 vue 作为参数传入。
该方法需要在调用 new vue() 之前被调用。
当 install 方法被同一个插件多次调用,插件将只会被安装一次。
也就是说,我们在自己造的myrouter
里得实现这个install
方法
需求
- 提供一个构造类,能够使用
new vuerouter
来生成实例 - 实现install方法
- 监听
url
变化,并双向绑定current方法 - 注册自定义组件
router-link
与router-view
- 实现用户配置的路由数组到map的转换,方便快速的查询到路由匹配的对象
实现
let vue;//由于使用者肯定是使用vue.use引入的这个插件,所以代码里就不引入vue.js了,防止重复打包 // 需求1 声明一个拥有constructor构造器的class class vuerouter{ constructor(options={}){// 构造函数 this.$options=options;// 保存配置项 this.app = { // 声明一个拥有current的变量,已完成路由的双向绑定 current:"/" } vue.util.definereactive(this.app,'current',this.app.current);//vue的拦截方法,会该值增加get拦截以收集依赖,set拦截以触发双向绑定 this.routermap={}; // 创建key-value模式的routermap,便于使用key能够快速的找到即将render(渲染)的组件 this.init(options); // 执行init方法,以完成需求3,4,5 } init(options={}){ this.bindbrowserevents()// 绑定浏览器事件 this.initcomponent()//注册router-view及router-link组件 this.createroutermap(options.routes)//创建key-value模式的routermap } createroutermap(arr=[]){ // 创建routermap arr.foreach(item => { this.routermap[item.path]=item }); // 处理完后routermap格式如下 // this.routermap = { // '/':{ // path: '/', // name: 'home', // component: home // }, // '/about':{ // path: '/about', // name: 'about', // component: () => import(/* webpackchunkname: "about" */ '../views/about.vue') // } // } } bindbrowserevents(){ // hash模式监听 hashchange 方法 window.addeventlistener('load',this.onhashchange.bind(this)) window.addeventlistener('hashchange',this.onhashchange.bind(this)) } initcomponent(){ // 注册自定义组件routerlink及routerview vue.component('routerlink',{ props: { to: string }, render(h) { return h('a',{ attrs:{ href:'#'+this.to } },this.$slots.default) }, }) vue.component('routerview',{ render:(h)=>{ const component = this.routermap[this.app.current].component return h(component) }, }) } onhashchange(){ // hash变化时,改变 this.app.current window.location.hash = window.location.hash || '/'; // 如果hash没有值,则默认给补一个/#/ if(this.routermap[window.location.hash.slice(1)]){ // this.app.current = hash值 this.app.current = window.location.hash.slice(1); }else{ this.app.current = '/'; } // 此处执行完后,则由于双向绑定,会触发routerview进行重新渲染 } } // 需求2 实现install方法 vuerouter.install = function(_vue){ vue = _vue; // 因为一定会先走install,所以将这个传入的vue实例,保存到变量vue中 }
注释都写在代码里啦,可以执行简单的路由双向绑定功能,有哪里有疑问可以提出~互相学习~
觉得好的话,可以给我的 github点个star
哦