乐趣区

vuerouter工作原理概述和问题分析

问题

1. 为什么 url 变更时,router-view 组件就能渲染出在 routes 中定义的与当前 path 匹配的组件?

  1. root vue 实例上定义了一个响应式属性 Vue.util.defineReactive(this, '_route', this._router.history.current)
  2. url 变更时,会匹配到最新的 route,并且会设置 this._routerRoot._route, 触发setter
  3. router-view 组件的 render 函数中有使用到 parent.$route, 也就是间接的触发了 this._routerRoot._routegetter
  4. this._routerRoot._routesetter触发时即会触发 router-view渲染 watcher, 再次渲染, 并且此时拿到的路由组件也是最新的。

本质上利用了 vue 的响应式属性,在 route 属性变更和 router-view 视图渲染之间建立关系。

route 变更 => render 重新渲染

2. router-view render 中使用到 parent.$route 为什么就会被 this._routerRoot._route 收集 watcher

在挂载 router-view 组件时,会生成一个 watcher, router-view 的 render 函数中又触发了_route 的 getter 方法,那么此 watcher 就被收集到_route 的 Dep 中了。

在_route 的 setter 触发时,自然执行了这个 watcher, 组件重新 render。

在 vue 的仓库中 vue/src/core/instance/lifecycle.jsmountComponent方法中,能看到挂载组件时是会生成一个 watcher 的:

export function mountComponent(
    vm: Component,
    el: ?Element,
    hydrating?: boolean
) {
    ...
    let updateComponent 
    updateComponent = () => {vm._update(vm._render(), hydrating)
    }
    
    new Watcher(vm, updateComponent, noop, {before() {...}
    })
    ...
    return vm
}

3. this.$router, this.$route 是怎么挂载每个 vue 组件上的?

  Object.defineProperty(Vue.prototype, '$router', {get () {return this._routerRoot._router}
  })

  Object.defineProperty(Vue.prototype, '$route', {get () {return this._routerRoot._route}
  })

4. 替换 routes 的写法(这样写为什么有用)

// 替换现有 router 的 routes
router.matcher = new VueRouter({routes: newRoutes}).matcher

router.matcher 是比较核心的一个属性。对外提供两个方法 match(负责 route 匹配), addRoutes(动态添加路由)。

具体原因 :在做路径切换transitionTo 方法中,首先就会使用 const route = this.router.match(location, this.current) 来匹配 route, 其实内部会使用 matcher 来做匹配。修改了 watcher 即新的 routes 生效。

对 router.matcher 属性做修改,即新的 routes 就会替换老的 routes, 其实就是 replaceRoutes() 的含义。

export type Matcher = {match: (raw: RawLocation, current?: Route, redirectedFrom?: Location) => Route;
  addRoutes: (routes: Array<RouteConfig>) => void;
};

5. router-view 是什么

<router-view> 组件是一个 functional 组件,渲染路径匹配到的视图组件。<router-view> 渲染的组件还可以内嵌自己的 <router-view>,根据嵌套路径,渲染嵌套组件。

<transition>
  <keep-alive>
    <router-view></router-view>
  </keep-alive>
</transition>

工作原理简要流程图

hashchange
-->
match route
-->
set vm._route
-->
<router-view> render()
-->
render matched component

主要流程

  1. 初始化

    1. 实例化 Router, options 作为属性,根据 mode 生成 HTML5History 实例,或 HashHistory 实例
    2. 数据劫持,this.router.route =》this.history.current
    3. 数据劫持,this.history.current.route 变化时,自动执行 render。
    4. 立即执行一次路由跳转(渲染路由组件)
  2. 路由监听

    1. HashHistory 监听 hashChange 事件,HTML5History 监听 popstate 事件
  3. 路由变化处理

    1. 两种方式触发路由变化

      1. a 标签 href 方法(url 先变化,会触发两个 history 监听的 hashChange 或 popstate 事件,然后进入路由变化处理)
      2. 调用 router 的 push, replace, go 方法(先进入路由变化处理,然后修改 url)
    2. 路由变化具体处理过程

      1. history.transitionTo(根据 path 匹配到相应的 route, 然后调度执行 hooks, 然后更新 this.current 属性,触发视图更新)
      2. history.confirmTransition(调度执行上一个 route 和下一个 route 的相关生命周期 hooks)
  4. router 的辅助 API

    1. push(先进行路由变化处理,在更新 url,使用 history.pushState)
    2. replace() 和 push 处理方式一致, 只是更新 url 使用 history.replaceState
    3. go 使用 history.go 方法

参考链接

https://cnodejs.org/topic/58d…

https://zhuanlan.zhihu.com/p/…

https://ustbhuangyi.github.io…【这个比较详细,但是废话太多,看不清重点】

退出移动版