实现一个vue-router
我们都知道Vue提供了两种路由方式(hash/history),这两种方法都是利用了浏览器自身的特性。
1.mode: hash -> www.router.com/#/myrouter hash的值就是 #/myrouter,
特点是虽然出现在url中,但是不会出现在http请求中,对后端没影响,其改变不会引起页面重新加载。
2.mode:history -> 利用了H5中 History Interface 新增的 pushState 和 replaceState方法来修改历史记录栈。
今天以hash为例,实现一个简版vue-router
首先明确一下需求
需求一:实现VueRouter这个类,里面要做的事儿有3点
1.处理路由选项
2.监控url变化(hashchange)
3.响应url的变化
需求二:实现install方法
1.$router的挂载
2.两个全局组件(router-link,router-view)
补充一下:
⑴我们使用router的时候,Vue.use(Router),从use方法底层可以知道,先把你传入的这个Router做判断,是否有install函数,有就执行,并传入Vue对象作为第一个参数
⑵在install方法执行的时候,我们可以发现,这个时刻是非常早的,就是第一点中的Vue.use 时发生的,我们可以利用Vue.mixin 混入的方式推迟执行(beforeCreate的时机)从而做一些内容
开始启动!!!
1.先用vue-cli初始化带有vue-router一个项目,结构如下
2.然后我们在router文件夹下面新建一个myRouter.js,在router/index.js里面修改一下引用,换成我们自己的路径。
3.进入到我们的myRouter.js中,架子
class myRouter{
constructor(options) {
// 保存一下用户传入的路由表
this.$options = options
}
}
myRouter.install = function() {}
export default myRouter
4.先实现install方法,利用了Vue.mixin混入的方式来改变执行时机,确保有router并挂载上,保存一个变量Vue,一会有用!
myRouter.install = function(Vue) {
_Vue = Vue // 先保存,一会有用
// 挂载$router
Vue.mixin({
beforeCreate() {
// 此处this指的是组件实例 let vm = new Vue() 指向vm
// 利用了全局混入的方式,,在beforeCreate中来推迟执行时机
// 需要判断来保证传入了路由表router,因为并不是每一个组件都有router
if (this.$options.router) {
// $route 同理
Vue.prototype.$router = this.$options.router
}
}
})
// 2.定义两个全局组件router-link,router-view
Vue.component('router-link', {
props: {
to: {
type: String,
require: true
},
},
render(h) {
// 看看用法 <router-link to="/list">
// 看看用法 <a href="#/list">xxx</a>
// 看看用法 <a href={'#'+this.to}>{this.$slots.default}</a>
return h('a', {
attrs: {
href: '#' + this.to
}
}, this.$slots.default) // 默认内容,写在标签里的
}
})
Vue.component('router-view', {
render(h) {
// 先什么都不渲染 ,在第6点等你!
return h(null)
}
})
}
5.现在回到myRouter类里面,任务就是拿到hash,用户传入的路由表,对应上
class myRouter {
constructor(options) {
// 保存一下用户传入的路由表
this.$options = options
// routes: [{
// path: '/home',
// name: 'home',
// component: Home
// },
// {
// path: '/list',
// name: 'list',
// component: List
// }
// ]
// 建个对象,参照用户传入路由配置来拿到映射关系
// routeList = { /home: {path: '/home',...}} 有了这个对象后 通过hash来对应组件,以供渲染
this.routeList = {}
this.$options.routes.forEach(
route => {
this.routeList[route.path] = route
})
// console.log(this.routeList)
// 再来定义一个响应式的currentPath属性,截取掉hash的# 就行和routeList呼应上了,顺便给个默认值
const initPath = window.location.hash.slice(1) || '/'
// 当前hash 举例为 #/list 取到/list 做成响应式,否则只发生一次,做不到响应变化从而渲染对应组件
// 这个时候保存的_Vue 就有用了,通过底层提供的方法来设置,也可以外部引入Vue,太浪费了~
// 另外说一下在下面install保存的这个Vue变量,因为install执行的时间是非常早的,先Vue.use 再实例化的,此时就存在了 对吧!
_Vue.util.defineReactive(this, 'currentPath', initPath)
// 监控url变化 bind一下,预防一下 下面的上下文 调用问题
window.addEventListener('hashchange', this.onHashChange.bind(this))
}
onHashChange() {
// 监听这个方法来得到currentPath
// 只要#后面部分,呼应上!
this.currentPath = window.location.hash.slice(1)
}
}
6.看完注释后,我们记得之前全局组件router-view里面渲染的还是空呢,现在对号入组,监听的hash变了,currentPath变了就响应上了,然后渲染的组件就更新了~
Vue.component('router-view', {
render(h) {
// 找到当前url对应的组件,解构一下方便看
const { routeList, currentPath } = this.$router
const component = routeList[currentPath] ? routeList[currentPath].component : null
// 渲染对应的组件
return h(component)
}
})
大功告成,跳转,渲染都是OK的!