手写Vue-router源码:(hash模式简易版)

手写Vue-router源码:(hash模式简易版)


  1. 用户使用Vue.use()时,实际执行了模块中的install方法,而install方法可以接收到Vue的实例,而此时既可以在install方法中为Vue的实例上使用mixin去扩展相应的内容
let Vue;
// 创建Vue-Router的类
class MyRouter{

    static install(_Vue){
        Vue = _Vue;
        Vue.mixin({
            beforeCreate(){
                Vue.prototype.$myRouter = "来啦,小老弟!"
            }
        })
    }
}
export default MyRouter
  1. 以需求带动开发,以vue-router的简单配置开始进行开发:
new MyRouter({
    routes: [
        {
            path: "/",
            component: Home,
            beforEnter(from, to, next){
            	console.log(`beforeEnter from ${from} to ${to}`);
            	next();
            }
        },
        {
            path: "/about",
            component: () => import(/* webpackChunkName: "about" */ "./views/about.vue")
        }
    ]
})
  1. 框架单页应用的路由模式: (源码以hash模式为例)
  • hash模式: 使用url#后面的锚点来区分组件,hash改变的时候,页面不会被重新加载,但是会触发onhashchange事件
  • history模式: hash模式的url书写起来较丑,而使用mode:hitory模式的路由,这种路由充分利用了html5 history interface中新增的pushState()和replaceState()方法,这两个方法应用于浏览器的记录栈,在当前已有的back,forward,go基础之上,提供了对历史记录的修改功能。只是在当它们执行修改的时候,虽然会改变当前的url,但是浏览器并不会立即向服务端发送请求
    (1)初始化Router默认属性配置,构建基本的contructor()
    // 接收new Router传递的配置参数
    constructor(options){
        this.$options = options; // 缓存配置
        this.routeMap = {};      // 创建路由和页面映射的map
        // 使用Vue的响应式机制,来在路由切换的时候做一些相应
        this.app = new Vue({
            data: {
                current: '/'     // 当前的路由,默认为根路由
            }
        });
    }
  1. 初始化启动路由,由组件的use进行启动,在启动路由时做至少三件事: 监听onhashchange, 处理映射路由表,初始化组件
    // 启动整个路由,由组件use负责启动
    init(){
        // 1. 监听hashchange事件
        this.bindEvents();
        // 2. 处理路由表
        this.createRouteMap();
        // 3. 初始化组件: router-view, router-link
        this.initComponent();
        // 其他: 生命周期, 路由守卫 ... 后处理
    }

同时更新之前的install方法:

    static install(_Vue){
        Vue = _Vue;
        Vue.mixin({
            beforeCreate(){
                // 启动路由:
                if(this.$options.router){
                    // 入口
                    this.$options.router.init()
                }
            }
        })
    }
  1. 简单处理init中的方法:
    // 绑定事件
    bindEvents(){
        // 监听hashchang改变
        window.addEventListener("hashchange", this.onHashChange.bind(this), false)
        // 监听初始化页面
        window.addEventListener("load", this.onHashChange.bind(this), false)
    }
    onHashChange(e){
        console.log("hash改变", e)
    }

    // 处理路由表:
    createRouteMap(){
        // 根据路由做路由和配置之间的映射
        this.$options.routes.forEach(item => {
            this.routeMap[item.path] = item;
        })
    }

    // 注册组件, 这里先仅仅注册router-view组件:
    initComponent(){
        // 注册router-view组件
        Vue.component('router-view', {
            // 通过调用vue的render方法去渲染一个虚拟DOM组件
            render: h => {
                const component = this.routeMap[this.app.current].component;
                return h(component)
            }
        })
    }
  1. 处理hashchange监听,例用Vue的响应式机制完成基本的hash路由切换,此时基本完成最小执行版本
    // 获取当前的hash值
    getHash(){
        return window.location.hash.slice(1) || '/'
    }
    onHashChange(){
        // 1. 获取当前的hash值
        let hash = this.getHash();
        // 2. 修改this.app.current, 利用了Vue的响应式机制
        this.app.current = hash;
    }
  1. 实现router-link组件: 这里不采用template模板的形式,而是调用render函数,写为虚拟DOM形式构建组件:
    Vue.component('router-link', {
        props: {
            to: String
        },
        render(h) {
            return h('a', {
                attrs: {
                    href: '#' + this.to   // 转为hash路由
                },
                // 默认的组件插槽内容,取出作为动态虚拟DOM组件的子组件, 这里的this.$slots为当前组件内部的插槽内容
            }, [this.$slots.default]
            )
        }
    })
  1. 实现vue-router的路由的生命周期,实现beforeEnter生命周期事件,其他的生命周期不在实现,内容基本相似,只是执行时机的不同
    // 获取当前跳转的hash路由和跳转到的hash路由
    getFromAndTo(e){
        let from, to;
        if(e.newURL){
            // 这是一个hashChange的事件
            from = e.oldURL.split('#')[1];
            to = e.newURL.split("#")[1];
        }else{
            // 这是第一次加载页面load触发的事件
            from = "";
            to = this.getHash();
        }
        return {from, to}
    }
    // 简单修改hashChange事件
    onHashChange(e){
        // 1. 获取当前的hash值
        let hash = this.getHash();
        // 路由跳转开始,执行第一个生命周期
        let router = this.routeMap[hash];
        let {from, to} = this.getFromAndTo(e);
        if(router.beforeEnter){
            // 存在beforeEnter生命周期,则执行第一个声明周期
            router.beforeEnter(from, to, () => {
                this.app.current = hash;
            })
        }else{
            // 2. 修改this.app.current, 利用了Vue的响应式机制
            this.app.current = hash;
        }
    }
  • 完整代码: (这个是一个极简版本,关于完整的核心代码大家可以去github查看vue-router源码)
let Vue;
class MyRouter{
    static install(_Vue){
        Vue = _Vue;
        Vue.mixin({
            beforeCreate(){
                if(this.$options.router){
                    Vue.prototype.$router = this.$options.router
                    this.$options.router.init()
                }
            }
        })
    }
    constructor(options){
        this.$options = options;
        this.routeMap = {};
        this.app = new Vue({
            data: {
                current: '/'
            }
        });
    }
    init(){
        this.bindEvents();
        this.createRouteMap();
        this.initComponent();
    }
    bindEvents(){
        window.addEventListener("hashchange", this.onHashChange.bind(this), false)
        window.addEventListener("load", this.onHashChange.bind(this), false)
    }
    getHash(){
        return window.location.hash.slice(1) || '/'
    }
    getFromAndTo(e){
        let from, to;
        if(e.newURL){
            from = e.oldURL.split('#')[1];
            to = e.newURL.split("#")[1];
        }else{
            from = "";
            to = this.getHash();
        }
        return {from, to}
    }
    onHashChange(e){
        let hash = this.getHash();
        let router = this.routeMap[hash];
        let {from, to} = this.getFromAndTo(e);
        if(router.beforeEnter){
            router.beforeEnter(from, to, () => {
                this.app.current = hash;
            })
        }else{
            this.app.current = hash;
        }
    }
    createRouteMap(){
        this.$options.routes.forEach(item => {
            this.routeMap[item.path] = item;
        })
    }
    initComponent(){
        Vue.component('router-view', {
            render: h => {
                const component = this.routeMap[this.app.current].component;
                return h(component)
            }
        })
        Vue.component('router-link', {
            props: {
                to: String
            },
            render(h) {
                return h('a', {
                    attrs: {
                        href: '#' + this.to
                    },
                }, [this.$slots.default]
                )
            }
        })
    }
    push(url){
        // hash模式,直接赋值,如果时history模式,使用pushState
        window.location.hash = url;
    }
}
export default MyRouter
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据提供的引用内容,可以得出以下结论: 1. 引用中提到了一个关于Vue项目的命令行交互过程,包括项目名称、描述、作者等信息。这个阶段可能会安装一些依赖。 2. 引用[2]中显示了一段错误信息,指出找不到`vue-cli`模块。然后执行了`cnpm install -g vue-cli`命令来安装`vue-cli`。 3. 引用提到了`vue-router-next`是Vue 3的路由器库。它是Vue生态系统的一部分,并且是使用MIT许可的开源项目。 综上所述,根据引用内容,可以得出以下结论:在这个项目中,使用了`vue-route`版本为1.5.1和`vue-router`版本为3.5.2。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [vue demo 创建](https://blog.csdn.net/s470445610/article/details/89285982)[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^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [vue-router-next:Vue 3官方路由器](https://download.csdn.net/download/weixin_42131890/15108654)[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^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值