Vue-Router原理实现

router回顾

  • router的使用步骤:
    1. 创建路由相关的组件(视图)
    2. 注册路由插件,调用vue.use注册VueRouter
    3. 创建router对象,同时配置路由规则
    4. 注册router对象,就是在创建vue实例的时候,在选项里面配置创建好的router对象
    5. 通过router-view设置占位,当路径匹配成功之后会将匹配到的组件替换router-view的位置
    6. 通过router-link创建链接
    7. 补充:Vue.use的作用是注册组件,内部接收一个参数,如果参数是函数,则vue.use直接调用函数注册组件,如果传入的是对象的话。vue.use会调用对象的install方法注册插件。
    8. 补充:如果这里配置了router选项,那此时会给vue实例注入两个选项 r o u t e 路 由 规 则 , route路由规则, routerouter路由对象,通过路由对象可以调用一些相应的方法,如push等
    9. 补充:vue中 r o u t e r 和 router和 routerroute的区别: r o u t e : : 当 前 激 活 的 路 由 的 信 息 对 象 。 每 个 对 象 都 是 局 部 的 , 可 以 获 取 当 前 路 由 的 p a t h , n a m e , p a r a m s , q u e r y 等 属 性 。 route::当前激活的路由的信息对象。每个对象都是局部的,可以获取当前路由的 path, name, params, query 等属性。 routepath,name,params,queryrouter::全局的 router 实例。通过 vue 根实例中注入 router 实例,然后再注入到每个子组件,从而让整个应用都有路由功能。其中包含了很多属性和对象(比如 history 对象),任何页面也都可以调用其 push(), replace(), go() 等方法。
  • Hash模式和History模式
    两种都是客户端路由的实现方式,也就是当路径发生变化的时候不会向服务端发送请求,使用js监视路径的变化,然后根据不同的地址渲染不同的内容,如果需要服务端内容会发送ajax请求来获取。
    1. 两者表现形式的区别
      • Hash模式:路径中带着‘#’,#后面的内容作为路由地址,可以通过‘?’携带参数,这种url形式很丑
      • History模式:url是正常的url,但需要服务端配置支持。
    2. 原理的区别
      • Hash模式:基于锚点,以及onhashchange事件。通过锚点的参数作为路由地址,当路由地址放生变化的时候出发onhashchange事件,然后根据路径决定页面呈现内容。
      • History模式:是基于HTML5中的History API
        • history.pushState() IE10以后才支持
        • history.replaceState()
          pushState和push的区别是:当调用 history.push()的时候路径会发生变化,会向服务器发送请求,而调用history.pushState()的时候,不会向服务器发送请求,只会改变浏览器地址栏中的地址,并且把地址记录到历史记录中。所以通过pushState可以实现客户端路由,pushState有兼容性问题,如果想要兼容ie9以前的浏览器只能使用hash模式。
  • History模式的使用
    • History需要服务器的支持
    • 单页应用中,只有一个页面index.html,服务端不存在客户端路由这样的地址会返回找不到该页面 .
    • 在服务器端应该除了静态资源外都返回单页应用的index.html
    • 在nodejs中配置history模式
      app.js
    • 在Nginx中配置history模式
      nginx.conf
      执行过程:刷新浏览器之后会向服务器去请求路由地址,服务器接收到请求之后回去找路径对应的文件,当配置文件加了try_files之后,如果找不到对应的文件,会默认返回网站根目录中的index.html,但浏览器接收到网页之后会再去判断路由地址,然后在客户端解析路由地址对应的组件。
  • 回顾vue-router核心代码
    1. 导入VueRouter模块,因为VueRouter是Vue的插件。所以用vue.use注册这个插件,这里传入的是一个对象,所以需要实现install方法
    2. 创建路由对象。VueRouter应该是一个构造函数后或者是一个类,这个类应该有一个静态的install方法
    3. VueRouter的构造函数需要接受一个参数,参数是一个对象形式,对象里面传入的是理由的规则,路由规则核心记录的是路由的地址和核心的组件
    4. 创建一个vue的实例,在实例中传入创建好的router对象
    5. VueRouter的类图
      vueRouter类图
      三个属性:
      • options:记录构造函数中传入的对象
      • data:是一个对象,里面有一个属性current,用来记录当前路由地址的,目的是设置一个响应式的对象,路由地址发生变化时,对应的组件要相应的更新
      • routeMap:是一个对象,用来记录路由地址和组件的对应关系
        方法:
        +Constructor(Options) 初始化属性
        _install 静态方法,用来实现vue的插件机制
        +init用来调用下面的三个方法
        +initEvent 用来注册pushState事件,监听浏览器的变化
        +createRouteMap 初始化routemap属性,把构造函数中传入的路由规则转换成键值对的形式,赋值给routerMap
        +initComponents 用来创建router-link和router-view这两个组件的

手写VueRouter

history模式
let _Vue = null;
export default class VueRouter {
    // vue:vue的构造函数
    static install (vue) {
        // 1. 判断当前插件是否已经被安装
        if (VueRouter.install.installed) return;
        VueRouter.install.installed = true;
        // 2.把Vue构造函数记录到全局变量,将来在vuerouter中的一些实例方法还要使用构造函数
        _Vue = vue;
        // 3.把创建Vue实例时候传入的router对象注入到Vue实例上
        // _Vue.prototype.$router = this.$options.router
        // 混入
        _Vue.mixin({
            beforeCreate () { // 因为beforeCreate除了vue实例会执行,组件中也会执行,所以会执行很多次
                if (this.$options.router) { // 只有vue实例中的$options中才有router属性,可以排除其他组件进入
                    _Vue.prototype.$router = this.$options.router;
                    this.$options.router.init();
                }
            }
        })
    }
    constructor (options) {
        this.options = options;
        this.routeMap = {};
        this.data = _Vue.observable({ // 用observable创建响应式对象
            current: '/'
        })
    }
    createRouteMap () {
        // 遍历所有的路由规则,把路由规则解析成键值对的形式存储到routeMap中
        this.options.routes.forEach(route => {
            this.routeMap[route.path] = route.component
        })
    }
    init () {
        this.createRouteMap();
        this.initComponents(_Vue);
        this.initEvent()
    }
    initComponents (Vue) { 
        // 传vue构造方法的目的是减少这个方法和外部的依赖
        Vue.component('router-link', {
            props: {
                to: String
            },
            // h函数的作用是帮助创建虚拟dom
            // 三个参数 第一个参数:创建元素对应的选择器(这里使用标签选择器),第二个参数:标签的属性(对象),第三个参数:生成的子元素(数组)
            render (h) {
                return h('a', {
                    attrs: {
                        href: this.to
                    },
                    on: {
                        click: this.clickHandler
                    }
                }, [this.$slots.default])
            },
            methods: {
                clickHandler (e) {
                    // pushState三个参数:1:data是将来触发popState的时候传给popState对象的参数,2:网页的标题,3.当前要跳转的地址
                    history.pushState({}, '', this.to)
                    // this指的是router-link组件也是一个vue实例
                    this.$router.data.current = this.to
                    e.preventDefault()
                }
            }
            // template: '<a :href="to"><slot></slot></a>'
        })
        const self = this
        Vue.component('router-view', {
            render (h) {
                const component = self.routeMap[self.data.current]
                return h(component)
            }
        })
    }
    initEvent () {
        window.addEventListener('popstate', () => {
            this.data.current = window.location.pathname
        })
    }
}

hash模式

let _Vue = null;
export default class VueRouter {
    static install (vue) {
        // 1.判断当前插件是否已经被安装
        if (VueRouter.install.installed) return;
        VueRouter.install.installed = true;
        // 2.把vue构造函数记录到全局变量,将来在vuerouter中的一个实例方法还要使用全局对象
        _Vue = vue;
        // 3.把创建vue实例时传入的router对象注入到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.data = _Vue.observable({
            current: '/'
        })
        this.addSign(); // 增加锚点标识,以及注册锚点监听
    }
    addSign () {
        const hash = window.location.hash; 
        if (!hash.startsWith('#/')) { // 如果没有锚点标识,则增加锚点标识
            window.location.hash = '#/'
        } else { // 如果有锚点标识,则直接替换当前为获取到的锚点标识后的内容
            this.data.current = hash.substr(1);
        }
        // 监听页面路由变化
        window.onhashchange = () => {
            this.data.current = window.location.hash.substr(1)
        }
    }
    init () {
        this.createRouteMap();
        this.initComponents(_Vue);
    }
    createRouteMap () {
        this.options.routes.forEach(route => {
            this.routeMap[route.path] = route.component
        })
    }
    initComponents (vue) {
        vue.component('router-link', {
            props: {
                to: String
            },
            render(h) {
                return h('a', {
                    attrs: {
                        href: '#' + this.to
                    }
                }, [this.$slots.default] )
            }
        })
        const self = this;
        vue.component('router-view', {
            render (h) {
                const component = self.routeMap[self.data.current];
                return h(component)
            }
        })
    }
}

以上就是vue-router的相关学习内容啦,手写出VueRouter还是很有成就感的~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值