笔记-20220330-vuerouter简单实现

vuerouter简单实现

路由

hash 和 history

  • hash 模式基于锚点以及 onhashchange 事件

  • history 模式基于 html5 中的 history API
    使用 history.pushState()和 history.replaceState()实现 但是 IE10 以后才支持
    相较于 push 和 replace 两个方法 pushState 和 replaceState 不会向服务器请求,实现了客户端路由

node 中的 history

因为 vueCli 中的 devServer 默认配置支持了 histroy 路由 所以需要到 node 或 nginx 中查看具体的配置
在 node 中 不使用 history 支持的中间件时 在访问路由时会返回默认的 get error 页面
开启 history 中间件支持后 可以正常跳转

node 中的 history

nginx 中也会跳出 404 页面
nginx.conf 中 修改
http 下的 server 下的

location / {
    root html;
    index index.html index.htm;
    # 新添加
    try_files $uri $uri/ index.html;
}

vueRouter 实现

  • hash

    • hash 地址可以使用 location.url 切换 url 地址 如果只更改了#后的地址 浏览器不会请求服务器这个地址
    • hashchange 事件 找到对应路径的组件 重新渲染
  • history

    • historyAPI 用来改变浏览器 url 地址 同样不会向服务器请求
    • 监听 popState 事件 可以获取到浏览器地址的变化 (只能获取到前进或后退按钮 而 pushState 和 replaceState 触发的地址改变不会监听到 可以监听到 history.back 和 forward)
    • 找到对应地址的组件重新渲染
  • 实现分析

    • 前提:Vue.use() 传入函数时会直接调用 传入对象会调用对象的 install()

    • new VueRouter({routes: Array<path>}) : router

    • 类图

        • VueRouter
      • 属性
        • +options 记录构造器中传入的 options
        • +data 有 current 属性 记录当前路由地址 响应式对象(使用 Vue.Observable())
        • +routeMap 记录地址和组件的对应关系
      • 方法
        • +constructor(options):VueRouter
        • _install(Vue):void
        • +init():void
        • +initEvent():void 注册 popState 事件
        • +createRouterMap():void
        • +initComponent():void 创建 router-view 和 router-link 这两个组件
  • 实现

let _Vue;
export default class VueRouter {
    static isInstalled = false;
    static install (Vue) {
        // use还可以传递options 这里没有做
        // 判断当前插件是否已经被安装
        if (this.isInstalled) {
            return new Error("VueRouter has been installed!!");
        }
        // 需要保存Vue 因为之后还要创建router-view 和router-link这两个组件 全局注册组件component静态方法
        _Vue = Vue;
        // 因为所有的vue实例都有$router 需要将router注册到Vue实例中
        // _Vue.prototype.$router = this.$options.router 此时获取不到Vue实例
        // 利用混入特性 将router注入到Vue中
        //      同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
        _Vue.mixin({
            beforeCreate () {
                // 在beforeCreate钩子函数调用时 同时将router添加到Vue的原型上
                // 因为beforeCreate钩子函数在子组件实例化时也会执行  此时需要判断  组件则不执行添加操作
                // 创建Vue实例时 会传入(render,router,store等)  而组件实例化时则大部分什么都没有
                if (this.$options.router) {
                    _Vue.prototype.$router = this.$options.router;
                }
            },
        });
        this.isInstalled = true;
    }

    options;
    routeMap;
    data;
    constructor(options) {
        this.options = options;
        this.routeMap = {};
        this.data = _Vue.observable({
            // 为防止刷新页面时进入错误路由 这里直接获取到访问的路由进行初始化
            current: window.location.pathname,
        });
        this.init();
    }

    init () {
        this.createRouteMap();
        this.initComponent(_Vue);
        this.initEvent();
    }

    createRouteMap () {
        // 遍历路由 存储到routemap中
        this.options.routes.forEach((route) => {
            this.routeMap[route.path] = route.component;
        });
    }

    initComponent (Vue) {
        Vue.component('router-link', {
            props: {
                to: String,
            },
            methods: {
                clickHandler (e) {
                    history.pushState({}, '', this.to)
                    this.$router.data.current = this.to;
                    e.preventDefault();
                }
            },
            // 因为当前打包后使用的是运行时版本的vue 不能使用template  可以使用完整版vue (vueconfig 属性runtimecompiler 为true)
            // 如果想使用运行时版本
            // template: `<a :href="to"><slot></slot></a>`
            render (h) {
                return h('a', {
                    attrs: {
                        href: this.to,
                    },
                    on: {
                        click: this.clickHandler,
                    }
                }, [this.$slots.default])
                // 因为插槽没有起名字 所以使用默认插槽
            },
        });
        const self = this;
        Vue.component('router-view', {
            props: {
                to: String,
            },
            // 因为当前打包后使用的是运行时版本的vue 不能使用template  可以使用完整版vue (vueconfig 属性runtimecompiler 为true)
            // 如果想使用运行时版本
            // template: `<a :href="to"><slot></slot></a>`
            render (h) {
                const path = self.data.current;
                let component = self.routeMap[path];
                if (!component) component = self.routeMap['*']
                return h(component)
                // 因为插槽没有起名字 所以使用默认插槽
            },
        });
    }

    initEvent () {
        // 发现当点击浏览器的前进后退时不会改变画面 刷新时对应组件异常  所以要监听这些事件来改变视图
        window.addEventListener('popstate', () => {
            this.data.current = window.location.pathname;
        })
    }
}
  • 实现关键问题
    • 在使用混入对Vue原型操作时 判断这个beforeCreated是谁触发的 如果是组件等触发的不可以执行
      必须是有router这个配置项的祖先vm实例初始化调用beforeCreated才行 否则之后所有的vue实例都没有$router这个属性
    • 在router创建实例时 执行init 获取到routes并保存
    • 当前实现只是简单的非嵌套路由,如果嵌套路由需要对routes进行处理
    • error(404)画面需要在匹配组件失败后 使用404画面替换掉router-view
    • 响应式对象实现的data对象 和 h函数是实现router-view的主要
    • 。。。待补充
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值