vue 和react router实现原理

127 阅读3分钟

参考文章:神光

hash 原理

hash 是 URL 中 hash (#) 及后面的那部分,常用作锚点在页面内进行导航,改变 URL 中的 hash 部分不会引起页面刷新

通过 hashchange 事件监听 URL 的变化,改变 URL 的方式只有这几种:

  1. 通过浏览器前进后退改变 URL
  2. 通过<a>标签改变 URL
  3. 通过window.location改变URL

history 原理

history 提供了 pushState 和 replaceState 两个方法,这两个方法改变 URL 的 path 部分不会引起页面刷新

history 提供类似 hashchange 事件的 popstate 事件,但 popstate 事件有些不同

  • 通过浏览器前进后退改变 URL 时会触发 popstate 事件
  • 通过js 调用history的back,go,forward方法会触发该事件
  • 通过pushState/replaceState或<a>标签改变 URL 不会触发 popstate 事件。
  • 好在我们可以拦截 pushState/replaceState的调用和<a>标签的点击事件来检测 URL 变化

下面说一些重点:

我打开了一个新的标签页、然后访问 baidu.comsougou.comtaobao.com

1、最开始开的新标签页也占用一个栈的位置,所以history.length此时为5

image.png

2、路由导航api: history.back、history.forward history.go不会更改栈记录,只修改指针位置

除了用 history.back、history.forward 在 history 之间切换外,还可以用 history.go

参数值是 delta:

history.go(0) 是刷新当前页面。

history.go(1) 是前进一个,相当于 history.forward()

history.go(-1) 是后退一个,相当于 history.back()

当然,你还可以 history.go(-2)、histroy.go(3) 这种。

3、新增路由api: history.replaceState, history.pushState会修改栈记录

注意:

history.replaceState仅替换当前页路由

image.png

history.pushState会把后面的路由全被覆盖相当于产生了新的分支覆盖了旧分支

image.png

4、history.scrollRestoration 是用来保留滚动位置

有两个值 auto、manual,默认是 auto,也就是会自动定位到上次滚动位置,设置为 manual 就不会了。

5、popstate

当你在 history 中导航时,popstate 就会触发,比如 forwad、back、go。 但是 history.pushState、history.replaceState 这种并不会触发 popstate。

6、hashChange和popstate的区别

hashChange只能监听hash的变化,popState可以监听更多的东西

react router原理

react router 里,就只用到了 popstate 事件,没用到 hashchange 事件

初始渲染的流程

createBrowserRouter执行过程:

  1. 执行createRouter,

    这里传入了 history。这个不是原生的 history api,而是包装了一层之后的。

    关注 listen、push、replace、go 这 4 个方法就好了。listen就是就是监听了history的popState,而 push、replace、go 都是对 history 的 api 的封装。

    此外,history 还封装了 location 属性,不用自己从 window 取了。

  2. 然后 createRouter 里会对 routes 配置和当前的 location 做一次 match:

    matchRoutes 会把嵌套路由拍平,然后和 location 匹配

    然后就匹配到了要渲染的组件以及它包含的子路由

  3. 组件树渲染的时候,_randerMatches把 match 的这个结果渲染出来

image.png

切换路由的渲染流程

点击link切换路由时,

  1. 执行navigate
  2. 执行matchRoutes
  3. match 完会 pushState 或者 replaceState 修改 history,然后更新 state
  4. 然后触发了 setState,组件树会重新渲染

image.png

点击前进后退按钮的流程

这个就是监听 popstate,然后也做一次 navigate 就好了:

image.png

vue router原理

参考文章:

//myVueRouter.js
let Vue = null;
class HistoryRoute {
    constructor(){
        this.current = null
    }
}
class VueRouter{
    constructor(options) {
        this.mode = options.mode || "hash"
        this.routes = options.routes || [] //你传递的这个路由是一个数组表
        this.routesMap = this.createMap(this.routes)
        this.history = new HistoryRoute();
        this.init()

    }
    init(){
        if (this.mode === "hash"){
            // 先判断用户打开时有没有hash值,没有的话跳转到#/
            location.hash? '':location.hash = "/";
            window.addEventListener("load",()=>{
                this.history.current = location.hash.slice(1)
            })
            window.addEventListener("hashchange",()=>{
                this.history.current = location.hash.slice(1)
            })
        } else{
            location.pathname? '':location.pathname = "/";
            window.addEventListener('load',()=>{
                this.history.current = location.pathname
            })
            window.addEventListener("popstate",()=>{
                this.history.current = location.pathname
            })
        }
    }

    createMap(routes){
        return routes.reduce((pre,current)=>{
            pre[current.path] = current.component
            return pre;
        },{})
    }

}
VueRouter.install = function (v) {
    Vue = v;
    Vue.mixin({
        beforeCreate(){
            if (this.$options && this.$options.router){ // 如果是根组件
                this._root = this; //把当前实例挂载到_root上
                this._router = this.$options.router;
                Vue.util.defineReactive(this,"xxx",this._router.history)
            }else { //如果是子组件
                this._root= this.$parent && this.$parent._root
            }
            Object.defineProperty(this,'$router',{
                get(){
                    return this._root._router
                }
            });
            Object.defineProperty(this,'$route',{
                get(){
                    return this._root._router.history.current
                }
            })
        }
    })
    Vue.component('router-link',{
        props:{
            to:String
        },
        render(h){
            let mode = this._self._root._router.mode;
            let to = mode === "hash"?"#"+this.to:this.to
            return h('a',{attrs:{href:to}},this.$slots.default)
        }
    })
    Vue.component('router-view',{
        render(h){
            let current = this._self._root._router.history.current
            let routeMap = this._self._root._router.routesMap;
            return h(routeMap[current])
        }
    })
};

export default VueRouter

BrowserRouter与HashRouter的区别

1.底层原理不一样:

BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。

HashRouter使用的是URL的哈希值。

2.path表现形式不一样

BrowserRouter的路径中没有#,例如:localhost:3000/demo/test

HashRouter的路径包含#,例如:localhost:3000/#/demo/test

3.刷新后对路由state参数的影响

(1).BrowserRouter没有任何影响,因为state保存在history对象中。

(2).HashRouter刷新后会导致路由state参数的丢失!!!

4.备注:HashRouter可以用于解决一些路径错误相关的问题。