一 VueRouter本质
根据"不同的hash值"或者"不同的路径地址", 将不同的内容渲染到router-view中
所以实现VueRouter的核心关键点就在于如何监听'hash'或'路径'的变化, 再将不同的内容写到router-view中
模拟页面:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <a href="#/home">首页</a> <a href="#/about">关于</a> <div id="html"></div> <!-- <a onclick="go('/home')">首页</a> <a onclick="go('/about')">关于</a> <div id="html"></div> --> <script> // history模式需要注意的两个方法 function go(path) { // console.log(path); //这个方法可以改变浏览器路径,但是不刷新页面 history.pushState(null, null, path); document.querySelector('#html').innerHTML = path; } //解决点击 前进或后退按钮不响应的问题 window.addEventListener('popstate', ()=>{ console.log('点击了前进或者后退', location.pathname); document.querySelector('#html').innerHTML = location.pathname; }) // hash模式需要注意的两个方法 window.addEventListener('hashchange', ()=>{ // console.log('当前的hash值发生了变化'); let currentHash = location.hash.slice(1); //#/home document.querySelector('#html').innerHTML = currentHash; }) //解决哈希页面首次运行 不改变值的问题 window.addEventListener('load', ()=>{ let currentHash = location.hash.slice(1); document.querySelector('#html').innerHTML = currentHash; }) </script> </body> </html>
二 提取路由信息:
常规调用模式:
main.js
import Vue from 'vue' import App from './App.vue' import router from './router' Vue.config.productionTip = false new Vue({ router, render: h => h(App) }).$mount('#app')
router/index.js
import Vue from 'vue' // import VueRouter from 'vue-router' import VueRouter from './Nue-Router' import Home from '../views/Home.vue' import About from '../views/About.vue' Vue.use(VueRouter) const routes = [ { path: '/home', name: 'Home', component: Home }, { path: '/about', name: 'About', component: About } ] const router = new VueRouter({ mode: 'history', // #/home /home base: process.env.BASE_URL, routes }) export default router
Nue-Router.js
class NueRouter { constructor(options){ this.mode = options.mode || 'hash'; this.routes = options.routes || []; // 提取路由信息 /* { '/home': Home, '/about': About } * */ this.routesMap = this.createRoutesMap(); console.log(this.routesMap); } createRoutesMap(){ return this.routes.reduce((map, route)=>{ map[route.path] = route.component; return map; }, {}) } } NueRouter.install = (Vue, options)=>{ } export default NueRouter;
三 初始化路由信息
class NueRouteInfo { constructor(){ this.currentPath = null; } } class NueRouter { constructor(options){ this.mode = options.mode || 'hash'; this.routes = options.routes || []; // 提取路由信息 /* { '/home': Home, '/about': About } * */ this.routesMap = this.createRoutesMap(); // console.log(this.routesMap); this.routeInfo = new NueRouteInfo(); // 初始化默认的路由信息 this.initDefault(); } initDefault(){ if(this.mode === 'hash'){ // 1.判断打开的界面有没有hash, 如果没有就跳转到#/ if(!location.hash){ location.hash = '/'; } // 2.加载完成之后和hash发生变化之后都需要保存当前的地址 window.addEventListener('load', ()=>{ this.routeInfo.currentPath = location.hash.slice(1); }); window.addEventListener('hashchange', ()=>{ this.routeInfo.currentPath = location.hash.slice(1); console.log(this.routeInfo); }); }else{ // 1.判断打开的界面有没有路径, 如果没有就跳转到/ if(!location.pathname){ location.pathname = '/'; } // 2.加载完成之后和history发生变化之后都需要保存当前的地址 window.addEventListener('load', ()=>{ this.routeInfo.currentPath = location.pathname; }); window.addEventListener('popstate', ()=>{ this.routeInfo.currentPath = location.pathname; console.log(this.routeInfo); }); } } createRoutesMap(){ return this.routes.reduce((map, route)=>{ map[route.path] = route.component; return map; }, {}) } } NueRouter.install = (Vue, options)=>{ } export default NueRouter;
举例使用:
<template> <div id="app"> <!-- <div id="nav"> <router-link to="/home">Home</router-link> | <router-link to="/about">About</router-link> </div> <router-view></router-view> --> <a href="#/home">首页111</a> <a href="#/about">关于222</a> </div> </template> <!-- 1.Vue-Router本质 根据"不同的hash值"或者"不同的路径地址", 将不同的内容渲染到router-view中 所以实现VueRouter的核心关键点就在于如何监听'hash'或'路径'的变化, 再将不同的内容写到router-view中 --> <style> </style>
四 注入全局属性,得到$router和$route
import router from "./index"; class NueRouteInfo { constructor(){ this.currentPath = null; } } class NueRouter { constructor(options){ this.mode = options.mode || 'hash'; this.routes = options.routes || []; // 提取路由信息 this.routesMap = this.createRoutesMap(); this.routeInfo = new NueRouteInfo(); // 初始化默认的路由信息 this.initDefault(); } initDefault(){ if(this.mode === 'hash'){ // 1.判断打开的界面有没有hash, 如果没有就跳转到#/ if(!location.hash){ location.hash = '/'; } // 2.加载完成之后和hash发生变化之后都需要保存当前的地址 window.addEventListener('load', ()=>{ this.routeInfo.currentPath = location.hash.slice(1); }); window.addEventListener('hashchange', ()=>{ this.routeInfo.currentPath = location.hash.slice(1); console.log(this.routeInfo); }); }else{ // 1.判断打开的界面有没有路径, 如果没有就跳转到/ if(!location.pathname){ location.pathname = '/'; } // 2.加载完成之后和history发生变化之后都需要保存当前的地址 window.addEventListener('load', ()=>{ this.routeInfo.currentPath = location.pathname; }); window.addEventListener('popstate', ()=>{ this.routeInfo.currentPath = location.pathname; console.log(this.routeInfo); }); } } createRoutesMap(){ return this.routes.reduce((map, route)=>{ map[route.path] = route.component; return map; }, {}) } } NueRouter.install = (Vue, options)=>{ Vue.mixin({ beforeCreate(){ //根组件在main.js中创建,已经有了router if(this.$options && this.$options.router){ this.$router = this.$options.router; this.$route = this.$router.routeInfo; }else{ //子组件由父组件传递 this.$router = this.$parent.$router; this.$route = this.$router.routeInfo; } } }); } export default NueRouter;
五 自定义路由组件 router-link
import router from "./index"; class NueRouteInfo { constructor(){ this.currentPath = null; } } class NueRouter { constructor(options){ this.mode = options.mode || 'hash'; this.routes = options.routes || []; // 提取路由信息 this.routesMap = this.createRoutesMap(); this.routeInfo = new NueRouteInfo(); // 初始化默认的路由信息 this.initDefault(); } initDefault(){ if(this.mode === 'hash'){ // 1.判断打开的界面有没有hash, 如果没有就跳转到#/ if(!location.hash){ location.hash = '/'; } // 2.加载完成之后和hash发生变化之后都需要保存当前的地址 window.addEventListener('load', ()=>{ this.routeInfo.currentPath = location.hash.slice(1); }); window.addEventListener('hashchange', ()=>{ this.routeInfo.currentPath = location.hash.slice(1); console.log(this.routeInfo); }); }else{ // 1.判断打开的界面有没有路径, 如果没有就跳转到/ if(!location.pathname){ location.pathname = '/'; } // 2.加载完成之后和history发生变化之后都需要保存当前的地址 window.addEventListener('load', ()=>{ this.routeInfo.currentPath = location.pathname; }); window.addEventListener('popstate', ()=>{ this.routeInfo.currentPath = location.pathname; console.log(this.routeInfo); }); } } createRoutesMap(){ return this.routes.reduce((map, route)=>{ map[route.path] = route.component; return map; }, {}) } } NueRouter.install = (Vue, options)=>{ Vue.mixin({ beforeCreate(){ if(this.$options && this.$options.router){ this.$router = this.$options.router; this.$route = this.$router.routeInfo; }else{ this.$router = this.$parent.$router; this.$route = this.$router.routeInfo; } } }); /* 只要外界使用了Vue-Router, 那么我们就必须提供两个自定义的组件给外界使用 只要外界通过Vue.use注册了Vue-Router, 就代表外界使用了Vue-Router 只要接通通过Vue.use注册了Vue-Router, 就会调用插件的install方法 所以我们只需要在install方法中注册两个全局组件给外界使用即可 * */ Vue.component('router-link', { props: { to: String }, render(){ /* 注意点: render方法中的this并不是当前实例对象, 而是一个代理对象 如果我们想拿到当前实例对象, 那么可以通过this._self获取
this._self代表这个router-link 组件 * */ // console.log(this._self.$router.mode); let path = this.to; if(this._self.$router.mode === 'hash'){ path = '#' + path; } return <a href={path}>{this.$slots.default}</a> } }); } export default NueRouter;
六 自定义路由出口组件 router-view
但是标红的一定要注意:这个方法会在router-view渲染之后 才执行,这样就无法拿到currentPath,所以把
$router 的属性变为 双向绑定,一修改(this.routeInfo属于$router的一个属性),页面就会重新渲染,也就是会让router-view重新渲染,从而拿到最新的currentPath
import router from "./index"; class NueRouteInfo { constructor(){ this.currentPath = null; } } class NueRouter { constructor(options){ this.mode = options.mode || 'hash'; this.routes = options.routes || []; // 提取路由信息 this.routesMap = this.createRoutesMap(); this.routeInfo = new NueRouteInfo(); // 初始化默认的路由信息 this.initDefault(); } initDefault(){ if(this.mode === 'hash'){ // 1.判断打开的界面有没有hash, 如果没有就跳转到#/ if(!location.hash){ location.hash = '/'; } // 2.加载完成之后和hash发生变化之后都需要保存当前的地址 window.addEventListener('load', ()=>{ console.log("BBBBBBB"); this.routeInfo.currentPath = location.hash.slice(1); }); window.addEventListener('hashchange', ()=>{ console.log("CCCCCCC"); this.routeInfo.currentPath = location.hash.slice(1); console.log(this.routeInfo); }); }else{ // 1.判断打开的界面有没有路径, 如果没有就跳转到/ if(!location.pathname){ location.pathname = '/'; } // 2.加载完成之后和history发生变化之后都需要保存当前的地址 window.addEventListener('load', ()=>{ console.log('load'); this.routeInfo.currentPath = location.pathname; }); window.addEventListener('popstate', ()=>{ this.routeInfo.currentPath = location.pathname; console.log(this.routeInfo); }); } } createRoutesMap(){ return this.routes.reduce((map, route)=>{ map[route.path] = route.component; return map; }, {}) } } NueRouter.install = (Vue, options)=>{ Vue.mixin({ beforeCreate(){ if(this.$options && this.$options.router){ this.$router = this.$options.router; this.$route = this.$router.routeInfo; /*这里 把this.$router 的所有属性都设置为双向绑定,这样当里面的属性改变 之后,会立即进行页面刷新,load方法会执行,得到currentPath,这样router-view 在渲染的时候,就会拿到正确的子组件 */ Vue.util.defineReactive(this, 'xxx', this.$router); }else{ this.$router = this.$parent.$router; this.$route = this.$router.routeInfo; } } }); /* 只要外界使用了Vue-Router, 那么我们就必须提供两个自定义的组件给外界使用 只要外界通过Vue.use注册了Vue-Router, 就代表外界使用了Vue-Router 只要接通通过Vue.use注册了Vue-Router, 就会调用插件的install方法 所以我们只需要在install方法中注册两个全局组件给外界使用即可 * */ Vue.component('router-link', { props: { to: String }, render(){ /* 注意点: render方法中的this并不是当前实例对象, 而是一个代理对象 如果我们想拿到当前实例对象, 那么可以通过this._self获取 * */ // console.log(this._self.$router.mode); let path = this.to; if(this._self.$router.mode === 'hash'){ path = '#' + path; } return <a href={path}>{this.$slots.default}</a> } }); Vue.component('router-view', { render(h){ // console.log('render'); let routesMap = this._self.$router.routesMap; let currentPath = this._self.$route.currentPath; // console.log(currentPath); let currentComponent = routesMap[currentPath]; console.log("AAAAAA",currentPath); return h(currentComponent); } }); } export default NueRouter;