手写VueRouter

109 阅读1分钟

手写VueRouter

需求分析

  • spa页面不能刷新
    • hash eg: #/about
    • History api eg: /about
  • 根据url显示对应内容
    • router-view
    • 数据响应式:current变量持有url地址,一旦变化,动态重新执行render

任务

  • 实现一个插件
    • 实现VueRouter类
      • 处理路由选项
      • 监控url变化,hashchange
      • 响应这个变化
    • 实现install方法
      • $router注册
      • 两个全局组件
/* eslint-disable */
let _Vue
// 实现一个插件
class VueRouter {
    // options选项:routes - 路由表 mode 及其他
    constructor(options) {
        this.$options = options

        //缓存path和route映射关系
        this.routeMap = {}
        this.$options.routes.forEach(route => {
            this.routeMap[route.path] = route
        });

        // 需要定义一个响应式的current属性
        const initial = window.location.hash.slice(1) || '/'
        // 给一个对象定义一个响应式数据
        _Vue.util.defineReactive(this, 'current', initial)

        // 监听url变化
        window.addEventListener('hashchange', this.onHashChange.bind(this))
    }
    onHashChange(){
        // 只要#后面部分
        this.current = window.location.hash.slice(1)
        console.log(this.current);
    }
}
// 返回一个函数或者返回一个对象,他有一个install方法
VueRouter.install = function (Vue) {
    // 引用Vue构造函数,在上面VueRouter中使用 处理响应式数据
    _Vue = Vue

    // 全局混入 为每个创建的vue实例混入配置对象
    Vue.mixin({
        beforeCreate() {
            // this.$options 拿到vue实例配置对象 vue根实例有挂载router
            if (this.$options.router) {
                // 挂载$router
                Vue.prototype.$router = this.$options.router
            }
        },
    })

    // 2.定义两个全局组件 router-link, router-view
    // render函数比template优先级高 template未来也会转为render函数
    Vue.component('router-link', {
        // template: "<a></a>",
        props: {
            to: {
                type: String,
                require: true
            }
        },
        render(h) {
            // <router-link to="/about"></router-link>
            // <a href="#/about">xxx</a>
            // 方法1.return 返回jsx 要求版本支持jsx
            // return <a href = {'#' + this.to} >{this.$slots.default}</a>
            // 方法2 通用性更高
            return h('a',{
                attrs:{
                    href: '#' + this.to
                }
            }, this.$slots.default)
        },
    })


    Vue.component('router-view', {
        render(h) {
            const {routeMap, current} = this.$router
            let component = routeMap[current] ? routeMap[current].component : null

            //找到当前url对应的组件
            const route = this.$router.$options.routes.find(
                route => route.path === this.$router.current
            )
            console.log(route,'route');

            if(route){
                component = route.component
            }
            // 渲染组件
            return h(component)
        }
    })
}

export default VueRouter

代码截图

截屏2022-04-26 上午2.50.31.png

截屏2022-04-26 上午2.50.52.png

截屏2022-04-26 上午2.51.03.png