自定义插件vue-router简单实现hashRouter设计思路

步骤

1.挂载 vue.prototype.$router

2.声明两个组件

router-view this.$router.current=>component => h(component)

router-link h('a',{attrs:{href:'#'+this.to}},this.$slots.default)

3.url的监听:window hashchange的改变

4.定义响应式current,defineReactive()

实现VueRouter类

let Vue
// vueRouter是一个类,一个插件
class VueRouter {
    constructor(options) {
        this.$options = options
        
    }
}

VueRouter.install = function (_Vue) {
    //保存引用
    Vue = _Vue
    //挂在一下vueRouter到vue原型
    //利用全局混入,全局执行代码,在vue实例beforeCreate时获取到router,因为在main.js中生成vue实例在VueRouter挂载到Vue之后,所以常规无法获取到router,
    Vue.mixin({
        beforeCreate() {
            if (this.$options.router) { //避免每次实例创建都触发,只有根实例上存在的才触发。
                Vue.prototype.$router = this.$options.router
            }

        }
    })

    //声明两个全局组件,router-viewport,router-link
    Vue.component('router-link', {
        props: {
            to: {
                type: String,
                required: true
            }
        },
        //预编译情况下,template是不能使用的,这里要使用render
        // template:'<a>123</a>'
        render(h) {
            // return <a href={'#'+this.to}>{this.$slots.default}</a> jsx语法也可以使用
            return h('a', {
                attrs: { href: "#" + this.to }
            }, this.$slots.default) //this.$slots.default 获取内容
        }
    })
    Vue.component('router-view', {
        render(h) {
            return h('div', 'router-view')
        }
    })
}

export default VueRouter

这里有个难点,就是如何将router挂载到vue原型上,我们采用了mixin的用法,在vue实例beforeCreate时获取到router,并挂在到实例上。

怎么理解呢?在router.js中,Vue.use(VueRouter)触发install方法,但是在此时,并没有生成router,是在new VueRouter之后生成的router,并且挂载到了Vue,此时才有VueRouter,利用全局混入,延迟执行挂载到Vue原型上,这样就可以获取到router实例了。

import Vue from 'vue'
// import VueRouter from 'vue-router'
import VueRouter from '@/krouter/kvue-router'
Vue.use(VueRouter)//执行install方法,router还未被创建
const routes = [
    {
        path: '/home',
        name: 'home',
        component: () => import('@/components/Home.vue')
    },
    {
        path: '/about',
        name: 'about',
        component: () => import('@/components/About.vue')
    },
]

const router = new VueRouter({
    routes
})
export default router
import Vue from 'vue'
import App from './App.vue'
import router from './router/router'

Vue.config.productionTip = false

new Vue({
  router,
  render: h => h(App),
}).$mount('#app')

监听url变化,渲染组件

class VueRouter {
    constructor(options) {
        this.$options = options
        //声明一个响应式current
        //渲染函数如果压重复执行,必须依赖响应式数据
        Vue.util.defineReactive(this,'current',window.location.hash.slice(1) || '/') //需要#后面的部分
        //监听url变化
        window.addEventListener('hashchange', () => {
            this.current = window.location.hash.slice(1)
        })
    }
}

这里使用Vue工具类定义响应式current,必须是响应式current,否则不生效,在router-view处读取并渲染对应组件,在这里Vue的原型对象已经挂载了this.$router,this.$router又是VueRouter的实例,所以在这上面能够找到对应的current属性,所以在current变化的时候,在引用到current的地方都会被通知到,然后渲染组件。

Vue.component('router-view', {
        render(h) {
            const obj = this.$router.$options.routes.find(el=>this.$router.current==el.path)
            return h(obj.component)
        }
    })

但是每次都要去遍历循环字典,也不是很合理,我们可以优化一下,缓存一下path和route映射关系

路由嵌套

思路:参考源码思路,给当前routerView深度标记,然后根据当前页面路由获取当前路由数组,其中包括一级和二级路由,然后使用depth获取对应的组件,然后并渲染。

Vue.component('router-view', {
        render(h) {
            //标记当前router-view深度
            this.$vnode.data.routerView = true
            let depth = 0
            let parent = this.$parent
            while (parent) {
                const vnodeData = parent.$vnode && parent.$vnode.data
                if (vnodeData) {
                    if (vnodeData.routerView) {
                        //说明当前的parent是一个routerView
                        depth++
                    }
                }
                parent = parent.$parent
            }


            let component = null
            const route = this.$router.matched[depth]
            if (route) {
                component = route.component
            }
            return h(component)
        }
    })

此时我们不再使用current来做响应式,使用matched数组获取匹配关系,VueRouter实例创建时调用match方法,获取路由数组,并且在路由发生变化时重新获取路由数组matched。

class VueRouter {
    constructor(options) {
        this.$options = options
        //声明一个响应式current
        //渲染函数如果压重复执行,必须依赖响应式数据
        // Vue.util.defineReactive(this,'current',window.location.hash.slice(1) || '/') //需要#后面的部分
        this.current = window.location.hash.slice(1) || '/' //初始值
        Vue.util.defineReactive(this, 'matched', [])
        // match方法可以递归的遍历路由表获取匹配关系的数组
        this.match()

        //监听url变化
        window.addEventListener('hashchange', () => {
            this.current = window.location.hash.slice(1)
            this.matched=[]
            this.match()
        })
    }
    match(routes) {
        routes = routes || this.$options.routes
        console.log(routes);

        //递归遍历路由表
        for (const route of routes) {
            if (this.current.indexOf(route.path)!=-1) {
                this.matched.push(route)
                if (route.children) {
                    this.match(route.children)
                }
                return
            }
        }
    }
}

附完整代码:

//vue-router插件
let Vue
// vueRouter是一个类,一个插件
class VueRouter {
    constructor(options) {
        this.$options = options
        //声明一个响应式current
        //渲染函数如果压重复执行,必须依赖响应式数据
        // Vue.util.defineReactive(this,'current',window.location.hash.slice(1) || '/') //需要#后面的部分
        this.current = window.location.hash.slice(1) || '/' //初始值
        Vue.util.defineReactive(this, 'matched', [])
        // match方法可以递归的遍历路由表获取匹配关系的数组
        this.match()

        //监听url变化
        window.addEventListener('hashchange', () => {
            this.current = window.location.hash.slice(1)
            this.matched=[]
            this.match()
        })
    }
    match(routes) {
        routes = routes || this.$options.routes

        //递归遍历路由表
        for (const route of routes) {
            if (this.current.indexOf(route.path)!=-1) {
                this.matched.push(route)
                if (route.children) {
                    this.match(route.children)
                }
                return
            }
        }
        console.log(this.matched);
    }
}

VueRouter.install = function (_Vue) {
    //保存引用
    Vue = _Vue
    //挂在一下vueRouter到vue原型
    //利用全局混入,全局执行代码,在vue实例beforeCreate时获取到router,因为在main.js中生成vue实例在VueRouter挂载到Vue之后,所以常规无法获取到router,
    Vue.mixin({
        beforeCreate() {
            if (this.$options.router) { //避免每次实例创建都触发,只有根实例上存在的才触发。
                Vue.prototype.$router = this.$options.router
            }

        }
    })

    //声明两个全局组件,router-viewport,router-link
    Vue.component('router-link', {
        props: {
            to: {
                type: String,
                required: true
            }
        },
        //预编译情况下,template是不能使用的,这里要使用render
        // template:'<a>123</a>'
        render(h) {
            // return <a href={'#'+this.to}>{this.$slots.default}</a> jsx语法也可以使用
            return h('a', {
                attrs: { href: "#" + this.to }
            }, this.$slots.default) //this.$slots.default 获取内容
        }
    })
    Vue.component('router-view', {
        render(h) {
            //标记当前router-view深度
            this.$vnode.data.routerView = true
            let depth = 0
            let parent = this.$parent
            while (parent) {
                const vnodeData = parent.$vnode && parent.$vnode.data
                if (vnodeData) {
                    if (vnodeData.routerView) {
                        //说明当前的parent是一个routerView
                        depth++
                    }
                }
                parent = parent.$parent
            }


            let component = null
            console.log(this.$router.matched);
            const route = this.$router.matched[depth]
            if (route) {
                component = route.component
            }
            return h(component)
        }
    })
}

export default VueRouter
import Vue from 'vue'
// import VueRouter from 'vue-router'
import VueRouter from '@/krouter/kvue-router'
Vue.use(VueRouter)//执行install方法,router还未被创建
const routes = [
    {
        path: '/home',
        name: 'home',
        component: () => import('@/components/Home.vue')
    },
    {
        path: '/about',
        name: 'about',
        component: () => import('@/components/About.vue'),
        children: [
            {
                path: '/about/info',
                component: {
                    render(h) {
                        return h('div', 'info page')
                    }
                }
            }
        ]
    },
]

const router = new VueRouter({
    routes
})
export default router

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
vue-router实现原理是利用浏览器提供的接口window.history和window.location.hash实现路由功能。具体来说,vue-router通过配置mode属性来选择使用哪个接口实现路由功能。mode属性有两个选项:hash和history。当mode为hash时,vue-router使用window.location.hash来监听URL的变化,并根据URL的hash值来匹配对应的组件。当mode为history时,vue-router使用HTML5的history API来监听URL的变化,并根据URL的路径来匹配对应的组件。通过这种方式,vue-router能够在不刷新页面的情况下更新视图,实现前端路由的功能。\[1\]\[2\] 另外,vue-router还支持懒加载的实现方式。最常用的懒加载方式是通过import()来实现。通过在路由配置中使用import()来动态加载组件,可以实现按需加载,提高应用的性能。例如,可以将组件的import语句放在路由配置中的component属性中,当路由被访问时,才会动态加载对应的组件。这种方式可以减少初始加载的资源量,提高应用的加载速度。\[3\] #### 引用[.reference_title] - *1* [vue-router实现原理](https://blog.csdn.net/mengweiping/article/details/101068638)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [超详细的vue-router原理](https://blog.csdn.net/jiangjialuan2/article/details/124799307)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Goat恶霸詹姆斯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值