vue-router 的学习以及实现

16 篇文章 1 订阅

这里记录一下关于 vue-router 的学习,以及自己实现一个类似的东西

vue-router 分为 hash 模式和 history 模式,众所周知的是,hash 模式 是利用 监听 hashchange 事件来实现的,而 history 则是通过监听 popstate 事件 实现的,那么 到底是在什么时候实现监听的呢?

1、创建一个 VueRouter 类,并实现内部的 事件注册

let _Vue
export default class VueRouter {
    // 使用静态方法,来让 vue 实现注册
    static install(Vue) {
        // 1、判断当前插件是否已经被安装
        if (VueRouter.install.installed) {
            return
        }
        VueRouter.install.installed = true
        // 2、把Vue 构造函数记录到全局变量
        _Vue = Vue
    }
    constructor(options) {
        this.options = options
        this.mode = options.mode || 'hash'
        // 路由对象,格式为: {地址:组件}
        this.routeMap = {}
        // 将当前的 data 手动设置为 响应式的 
        this.data = _Vue.observable({
            current: '/'
        })
    }
}

2、获取 vue 初始化时候注入的 router 对象

如下, router 是在vue 初始化的时候放到 vue 对象里的,那么怎么在 VueRouter 里面获得这个类呢?

import Vue from 'vue'
import App from './App.vue'
import router from './router'

Vue.config.productionTip = false
new Vue({
// ======== router 是在 vue 初始化的时候进行设置的  ===========
  router,
  render: h => h(App),
}).$mount('#app')

可以在这里利用混入(mixin) 来获得 对应的 router 属性

static install(Vue) {
        。。。
        _Vue = Vue
        // 3、把创建Vue 实例的时候传入 router 对象
        // 混入。
        _Vue.mixin({
            // 只在Vue 的实例之中安装,不在组件内安装
            // 这样每个组件都能有 这个样一个东西了
            beforeCreate() {
                if (this.$options.router) {
                    // 在初始化的时候,也就是 beforeCreate 生命周期获取 注入的 router 对象
                    _Vue.prototype.$router =  this.$options.router
                    this.$options.router.init()
                    // 判断当前是 hash 模式,还是 history 模式,
                    // 如果是 hash 模式的话,给初始化的 路径加上 #/
                    if (!window.location.hash && this.$options.router.mode === 'hash') {
                        history.replaceState({}, '', '#/')
                    }
                }
            }
        })
    }

3、初始化路由内部的属性和事件

   init() {
        // 初始化路由对象
        this.initRouteMap()
        // 初始化全局的 vue 组件
        this.initComponents(_Vue)
        // 初始化路由事件
        this.initEvent()
    }

①、初始化路由对象

  initRouteMap() {
        // 遍历所有的路由规则,然后放到 routeMap 中
        this.options.routes.forEach(route => {
            this.routeMap[route.path] = route.component
        })
    }

②初始化vue 内部组件

这里的路由组件包括了 router-link router-view ,关于 slot 属性不熟悉的,可以看一下 vue 中的 $slot 获取 插槽的节点

  initComponents(Vue) {
        let mode = this.mode === 'hash' ? '/#' : ''
        Vue.component('router-link', {
            props: {
                to: String
            },
            // 使用了 vue 的 render 属性,也相当于是函数式组件
            render(h) {
                return h('a', {
                    attrs: {
                        href: this.to,
                    },
                    on: {
                        click: this.clickHandler
                    }
                }, [this.$slots.default])
            },
            methods: {
               // 阻止 a 标签的 默认事件,然后 使用 history 属性设置 路由
                clickHandler(e) {
                    e.preventDefault()
                    history.pushState({}, '', mode + this.to)
                    // 注意的是,这里的 data.current 在前面已经被手动设置为 被监听的属性了
                    this.$router.data.current = this.to;
                } 
            }
        })
        const _this = this
        Vue.component('router-view', {
            render(h) {
               // 由于这里的 data.current 被手动监听了,所以这里的依赖会被收集起来,然后被改变的时候,动态改变这里的属性
                const component = _this.routeMap[_this.data.current]
                return h(component)
            }
        })
    }

③监听浏览器事件

  initEvent() {
        // 监听路由变化的时候,把当前的路由赋值给 当前的值
        // 比如点击 浏览器的前进和后退
        if (this.mode === 'hash') { // 当为 hash模式的时候
            window.addEventListener('hashchange', () => {
                this.data.current = window.location.hash.substr(1)
            })
        } else { // 当为 history 模式的时候
            window.addEventListener('popstate', () => {
                this.data.current = window.location.pathname
            })
        }
    }

 

4、完整代码

let _Vue
export default class VueRouter {
    static install(Vue) {
        // 1、判断当前插件是否已经被安装
        if (VueRouter.install.installed) {
            return
        }
        VueRouter.install.installed = true
        // 2、把Vue 构造函数记录到全局变量
        _Vue = Vue
        // 3、把创建Vue 实例的时候传入 router 对象
        // 混入。
        _Vue.mixin({
            // 只在Vue 的实例之中安装,不在组件内安装
            // 这样每个组件都能有 这个样一个东西了
            beforeCreate() {
                if (this.$options.router) {
                    _Vue.prototype.$router =  this.$options.router
                    this.$options.router.init()
                    if (!window.location.hash && this.$options.router.mode === 'hash') {
                        history.replaceState({}, '', '#/')
                    }
                }
            }
        })
    }
    constructor(options) {
        this.options = options
        this.mode = options.mode || 'hash'
        // 路由地址:组件
        this.routeMap = {}
        // 响应式的 
        this.data = _Vue.observable({
            current: '/home1'
        })
    }
    init() {
        // 初始化路由对象
        this.initRouteMap()
        // 初始化全局的 vue 组件
        this.initComponents(_Vue)
        // 初始化路由事件
        this.initEvent()
    }
    initRouteMap() {
        // 遍历所有的路由规则,然后放到 routeMap 中
        this.options.routes.forEach(route => {
            this.routeMap[route.path] = route.component
        })
    }
    // 初始化 router-link
    initComponents(Vue) {
        let mode = this.mode === 'hash' ? '/#' : ''
        Vue.component('router-link', {
            props: {
                to: String
            },
            render(h) {
                return h('a', {
                    attrs: {
                        href: this.to,
                    },
                    on: {
                        click: this.clickHandler
                    }
                }, [this.$slots.default])
            },
            methods: {
                clickHandler(e) {
                    e.preventDefault()
                    history.pushState({}, '', mode + this.to)
                    this.$router.data.current = this.to;
                } 
            }
            // tempalet: '<a :href="to"><slot></slot></a>'
        })
        const _this = this
        Vue.component('router-view', {
            render(h) {
                const component = _this.routeMap[_this.data.current]
                return h(component)
            }
        })
    }
    initEvent() {
        // 监听路由变化的时候,把当前的路由赋值给 当前的值
        // 比如点击 浏览器的前进和后退
        if (this.mode === 'hash') {
            window.addEventListener('hashchange', () => {
                this.data.current = window.location.hash.substr(1)
            })
        } else {
            window.addEventListener('popstate', () => {
                this.data.current = window.location.pathname
            })
        }
    }
}

5、使用

// router/index.js

import VueRouter from '../../vue-router/index'
import Vue from 'vue'
Vue.use(VueRouter)

const routes = [
    {
        path: '/home1',
        component: () => import('../components/HelloWorld.vue')
    },
    {
        path: '/home2',
        component: () => import('../components/HelloWorld2.vue')
    }
]

export default new VueRouter({
    mode: 'hash',
    routes
})
// 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')

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值