手写vue-router
1、编写路由类 my-router.js
let Vue;
class MyRouter{
constructor($options){
// $options为 { routes = [ { path: '/', '路由': Index, name: '首页' }] }
this.$options = $options
// 定义响应式的属性current
const initial = window.location.hash.slice(1) || '/'
Vue.util.defineReactive(this, 'current', initial)
// 监听hashchange事件
window.addEventListener('hashchange', this.onHashChange.bind(this))
// 路由表:缓存path和route映射关系
this.routeMap = {}
// 每个路由地址都对应自己的route
this.$options.routes.forEach(route => {
// '/': { path: '/', '路由': Index, name: '首页' }
this.routeMap[route.path] = route
});
}
onHashChange() {
// 当前hash去掉#
this.current = window.location.hash.slice(1)
}
}
// 定义插件
MyRouter.install = function(vue){
// 保存vue供上面响应式设置
Vue = vue
vue.mixin({
// 每个实例都会调用beforeCreate
beforeCreate(){
// 判断,拿到main.js中new vue()传递的router对象
if(this.$options.router){
vue.prototype.$router = this.$options.router
}
}
})
// 全局组件注册
Vue.component('router-link', {
props: {
to: {
type: String,
required: true
}
},
// runtime-only不带编译器,预编译,打包的时候编译。这里不能用template
// render函数只要用到响应式数据就会被依赖收集起来,等待重新调用
render(h){
return h(
'a',
{ attrs: { href: `#${this.to}` }, style: { display:'inline-block',padding:'0 10px' } },
[this.$slots.default]
)
}
})
// current发生改变render函数重新渲染,实现了组件切换
Vue.component('router-view', {
render(h) {
// 从router对象中拿到当前路由和路由表
// 如果当前路由有children,即路由有router-view,
// router-view会从current匹配当前路由,当前路由又是原来那个路由,无限循环
// 比如:/about有router-view,about要渲染router-view,后面router-view又要渲染about
const { routeMap, current } = this.$router
// 拿到当前路由对应的组件
const component = routeMap[current].component;
return h(component);
}
})
}
export default MyRouter
2、router/index.js引入路由,传递路由数组
import MyRouter from './my-router'
import vue from 'vue'
import About from '@/components/about'
import Index from '@/components/index'
vue.use(MyRouter)
const routes = [
{
path: '/',
component : Index,
name: '首页'
},
{
path: '/about',
component: About,
name: '关于'
}
]
const router = new MyRouter({ routes })
export default router
main.js挂载路由实例
使用自定义路由
1、编写组件app.vue
2、index.vue
<template>
<div>index</div>
</template>
<script>
</script>
<style>
</style>
3、about.vue
<template>
<div>
<div>about</div>
</div>
</template>
<script>
</script>
<style>
</style>
效果
about路由加入children
- 正如上面所提到的如果当前路由有children,即路由有router-view,router-view会从current匹配当前路由,当前路由又是原来那个路由,无限循环
- 比如:/about有router-view,about要渲染router-view,后面router-view
info组件和上面about组件一样简单用div显示:info
更新my-router.js
let Vue;
class MyRouter{
constructor($options){
// $options为 { routes = [ { path: '/', '路由': Index, name: '首页' }] }
this.$options = $options
// 初始化当前页面hash
this.current = window.location.hash.slice(1) || '/'
// 定义响应式的属性matched,所有使用到matched的页面都会收集依赖,matched更新,页面更新
Vue.util.defineReactive(this, 'matched', [])
this.match()
// 监听hashchange事件
window.addEventListener('hashchange', this.onHashChange.bind(this))
}
onHashChange() {
// 当前hash去掉#
this.current = window.location.hash.slice(1)
// 页面跳转重新获取匹配路由数组
this.matched = []
this.match()
}
match(routes){
routes = routes || this.$options.routes
// 递归遍历
for(const route of routes){
if(route.path === '/' && this.current === '/'){
this.matched.push(route)
return
}
// /about/info
if(route.path !== '/' && this.current.indexOf(route.path) !== -1){
this.matched.push(route)
if(route.children){
this.match(route.children)
}
return
}
}
}
}
// 定义插件
MyRouter.install = function(vue){
// 保存vue供上面响应式设置
Vue = vue
vue.mixin({
// 每个实例都会调用beforeCreate
beforeCreate(){
// 判断,拿到main.js中new vue()传递的router对象
if(this.$options.router){
vue.prototype.$router = this.$options.router
}
}
})
// 全局组件注册
Vue.component('router-link', {
props: {
to: {
type: String,
required: true
}
},
// runtime-only不带编译器,预编译,打包的时候编译。这里不能用template
// render函数只要用到响应式数据就会被依赖收集起来,等待重新调用
render(h){
return h(
'a',
{ attrs: { href: `#${this.to}` }, style: { display:'inline-block',padding:'0 10px' } },
[this.$slots.default]
)
}
})
// current发生改变render函数重新渲染,实现了组件切换
Vue.component('router-view', {
render(h) {
// 当前是个router-view
this.$vnode.data.routerView = true
// 标记当前router-view深度
let depth = 0;
let parent = this.$parent
while(parent){
const vnodeData = parent.$vnode && parent.$vnode.data
if(vnodeData){
if(vnodeData.routerView){
// 说明当前parent是一个router-view
// 有多少父router-view,自己深度+多少
depth++
}
}
parent = parent.$parent
}
// 获取path对应的component
let component = null;
const route = this.$router.matched[depth]
if(route){
component = route.component
}
return h(component)
}
})
}
export default MyRouter
测试没毛病了
1
1
1
1
1
1
1
1
1
1