Vue-Router实现原理

如果对大佬有帮助,请给小弟一个赞哦。

简述

单页面的优缺点不在本文讲,只讲原理。实现单页面的功能有两种方式:

  1. 使用锚点,hashHistory
  2. 利用浏览器的browserHistory原理

hashHistory原理

添加hashchange监听

window.addEventListener(
    'hashchange',
    function() {
        // hash改变时,进行页面更换
    }
)
复制代码

改变hash

window.location.hash = 'aaa';
复制代码

browserHistory原理

添加popstate监听,可以监听到浏览器前进、后退。但不能监听到pushState、replaceState,所以在执行pushState、replaceState的时候进行页面更换。

window.addEventListener(
    'popstate',
    function() {
        // url改变时,进行页面更换
    }
)
复制代码

改变url,pushState、replaceState的具体文档可以去看MDN。

history.pushState({}, '', path);
history.replaceState({}, '', path);
复制代码

Vue-Router的实践

一、Router类收集数据,确定路由类型
  1. mode:确定采用哪种方式的路由;对HTML5History或HashHistory进行实例化。
  2. routes:收集所有的路由信息;
class Router {
    constructor(options) {
        this.options = options;
        let mode = options.mode || 'hash';
        this.routes = options.routes;
        switch (mode) {
            case 'history':
              this.history = new HTML5History(this, options.base)
              break
            case 'hash':
              this.history = new HashHistory(this, options.base, this.fallback)
              break
            default:
              // 报错
          }
    }
    ...
}
复制代码
二、HTML5History
  1. setupListeners方法会监听popstate,获取到当前的path
  2. push方法改变路由
class HTML5History {
    constructor (route, base) {
        this.current = '';
    }
    setupListeners() {
        const handleRoutingEvent = () => {
            this.current = location.pathname?.slice(1) || '/';
        }
        window.addEventListener(
            'popstate',
            handleRoutingEvent
        )
    }
    push (path) {
        history.pushState({}, '', path);
    }
}
复制代码
三、HashHistory
  1. setupListeners方法会监听hashchange,获取到当前的path
  2. push方法改变路由
class HashHistory {
    constructor (route, base) {
        this.current = '';
    }
    setupListeners() {
        const handleRoutingEvent = () => {
            this.current = location.hash?.slice(1) || '/';
        }
        window.addEventListener(
            'hashchange',
            handleRoutingEvent
        )
    }
    push (location) {
        window.location.hash = location
    }
}
复制代码
四、触发setupListeners

Router中会有一个init初始化,用来执行监听;push方法执行路由修改。

class Router {
    init() {
        this.history.setupListeners()
    }
    push (location) {
        this.history.push(location)
    }
}
复制代码

为保障阅读体验,先断开,讲一下vue怎样使用插件,然后回来继续写Vue-Router

使用Router(看main.js)

会发现router出现在app.$options中

const app = new Vue({
  el: '#app',
  router,
  store,
  render: h => h(App)
})
console.log(app)
复制代码

理解Vue.use

Vue.use(Router)执行,回调用插件Router的静态方法install,并把Vue当作参数传进去,所以我们的Router需要一个install的静态方法。

import Router from '@/utils/router'
Vue.use(Router)
复制代码
五、静态方法install
  1. 在组件实例化前beforeCreate阶段,把实例赋值给this._routerRoot,并执行init,触发上面的步骤四
  2. 设置劫持,访问this.$router,返回this._routerRoot._router
  3. 新增全局组件router-view,用来渲染路由组件
Router.install = function(Vue) {
    Vue.mixin({
        beforeCreate () {
            if (this.$options.router) {
                this._routerRoot = this
                this._router = this.$options.router
                this._router.init(this)
            } else {
                this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
            }
        }
    })
    Object.defineProperty(Vue.prototype, '$router', {
        get () { return this._routerRoot._router }
    })
    Vue.component('router-view',{
        render(h){
            return h(com) // com是内容组件
        }
    })

}
复制代码

目前我们设置了路由监听,注册了router-view组件,那么路由改变时,怎样把对应的组件渲染近router-view组件呢?

六、router-view内容切换
  1. 在外部作用域声明一个_Vue,在install执行时把Vue赋值给_Vue。
let _Vue;

Router.install = function(Vue) {
_Vue = Vue;
}
复制代码

  1. HashHistory、HTML5History使用_Vue创建实例
class HashHistory {
    constructor (route, base) {
        this.current = '';
        this.app = new _Vue({
            data() {
                return {
                    path: '/'
                }
            },
        })
    }
}
class HTML5History {
    constructor (route, base) {
        this.current = '';
        this.app = new _Vue({
            data() {
                return {
                    path: '/'
                }
            },
        })
    }
}
复制代码
  1. router-view组件使用步骤2创建的实例中的path,对所有路由信息筛选,找到对应的组件,作为内容。因为router-view组件用到了app.path,就添加了依赖。当app.path改变时,router-view组件重新执行。
Router.install = function(Vue) {
    ...
    Vue.component('router-view',{
        render(h){
            const path = this._routerRoot._router.history.app.path;
            const routes = this._routerRoot._router.routes;
            const route = routes.find((i) => i.path === `/${path}`)
            const com = route ? route.component : routes.find((i) => i.path === `/404`).component
            return h(com)
        }
    })

}
复制代码

  1. 监听到路由变化,改变this.app.path,使router-view组件重新执行。pushState不能触发popstate监听,所有单独修改this.app.path
class HTML5History {
    ...
    setupListeners() {
        const handleRoutingEvent = () => {
            this.current = location.pathname?.slice(1) || '/';
            this.app.path = this.current;
        }
        window.addEventListener(
            'popstate',
            handleRoutingEvent
        )
    }
    push (path) {
        history.pushState({}, '', path);
        this.app.path = path;
    }
}
class HashHistory {
    ...
    setupListeners() {
        const handleRoutingEvent = () => {
            this.current = location.hash?.slice(1) || '/';
            this.app.path = this.current;
        }
        window.addEventListener(
            'hashchange',
            handleRoutingEvent
        )
    }
    push (location) {
        window.location.hash = location
    }
}
复制代码

完整代码

let _Vue;

class HTML5History {
constructor (route, base) {
this.current = ‘’;
this.app = new _Vue({
data() {
return {
path: ‘/’
}
},
})
}
setupListeners() {
const handleRoutingEvent = () => {
this.current = location.pathname?.slice(1) || ‘/’;
this.app.path = this.current;
}
window.addEventListener(
‘popstate’,
handleRoutingEvent
)
}
push (path) {
history.pushState({}, ‘’, path);
this.app.path = path;
}
}
class HashHistory {
constructor (route, base) {
this.current = ‘’;
this.app = new _Vue({
data() {
return {
path: ‘/’
}
},
})
}
setupListeners() {
const handleRoutingEvent = () => {
this.current = location.hash?.slice(1) || ‘/’;
this.app.path = this.current;
}
window.addEventListener(
‘hashchange’,
handleRoutingEvent
)
}
push (location) {
window.location.hash = location
}
}
class Router {
constructor(options) {
this.options = options;
let mode = options.mode || ‘hash’;
this.routes = options.routes;
switch (mode) {
case ‘history’:
this.history = new HTML5History(this, options.base)
break
case ‘hash’:
this.history = new HashHistory(this, options.base, this.fallback)
break
default:
// 报错
}
}
init() {
this.history.setupListeners()
}
push (location) {
this.history.push(location)
}
}
Router.install = function(Vue) {
_Vue = Vue;
Vue.mixin({
beforeCreate () {
if (this.KaTeX parse error: Expected '}', got 'EOF' at end of input: …n">this</span>.options.router
this._router.init(this)
} else {
this._routerRoot = (this.KaTeX parse error: Expected 'EOF', got '&' at position 8: parent &̲amp;&amp; <span…parent._routerRoot) || this
}
}
})
Object.defineProperty(Vue.prototype, ‘$router’, {
get () { return this._routerRoot._router }
})

<span class="hljs-built_in">Object</span>.defineProperty(Vue.prototype, <span class="hljs-string">'$route'</span>, {
    get () { <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>._routerRoot._route }
})
Vue.component(<span class="hljs-string">'router-view'</span>,{
    <span class="hljs-function"><span class="hljs-title">render</span>(<span class="hljs-params">h</span>)</span>{
        <span class="hljs-keyword">const</span> path = <span class="hljs-built_in">this</span>._routerRoot._router.history.app.path;
        <span class="hljs-keyword">const</span> routes = <span class="hljs-built_in">this</span>._routerRoot._router.routes;
        <span class="hljs-keyword">const</span> route = routes.find(<span class="hljs-function">(<span class="hljs-params">i</span>) =&gt;</span> i.path === <span class="hljs-string">`/<span class="hljs-subst">${path}</span>`</span>)
        <span class="hljs-keyword">const</span> com = route ? route.component : routes.find(<span class="hljs-function">(<span class="hljs-params">i</span>) =&gt;</span> i.path === <span class="hljs-string">`/404`</span>).component
        <span class="hljs-keyword">return</span> h(com)
    }
})

}
export default Router;

复制代码

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值