介绍
vue-router是一个vue插件。其实质是在location.hash、location.replace、HTML5 historyAPI、window.onpopstate、window.onhashchange上做的封装,通过一定的规则映射到对应的方法上,同时通过监听变化,从而实现单页路由。
装载
使用use(VueRouter)注入Vue中,use方法会检测注入插件VueRouter内的install方法(浏览器环境会自动调用,node环境需要手动调用)
第一步:Install解析(对应目录结构的install.js)
该方法内主要做了以下三件事:
- 对Vue实例混入beforeCreate钩子操作(在Vue的生命周期阶段会被调用)
- 通过Vue.prototype定义router、route 属性(方便所有组件可以获取这两个属性)
注:$route为当前router跳转对象里面可以获取name、path、query、params等(只读)
$router为VueRouter实例,想要导航到不同URL,则使用$router.push方法 - Vue上注册router-link和router-view两个组件
第二步:生成router实例
生成实例过程中,主要做了以下两件事
- 根据配置数组(传入的routes)生成路由配置记录表。(根据传入的 routes 配置生成对应的路由 map,然后直接返回了 match 匹配函数)
- 根据不同模式生成监控路由变化的History对象
注:History类由HTML5History、HashHistory、AbstractHistory三类继承,默认为hash模式
history/base.js实现了基本history的操作
history/hash.js,history/html5.js和history/abstract.js继承了base,只是根据不同的模式封装了一些基本操作
第三步:生成vue实例
进入Vue的生命周期,第一步Vue-Router对Vue混入的beforeCreate钩子会执行。
一开始验证vue是否有router对象了,如果有,就不再初始化了。否则,先将routerRoot指向根组件,将router对象挂载到根组件元素_router上(this._routerRoot = this; this._router = this.$options.router)然后初始化,建立路由监控,劫持数据_route,一旦_route数据发生变化后,通知router-view执行render方法。
三步走完初始化结束,界面将显示默认首页。
文件结构
components下是两个组件router-view和router-link;history是路由方式的封装,提供三种方式;util下主要是各种功能类和功能函数;create-matcher和create-router-map是生成匹配表;index是VueRouter类,也整个插件的入口;Install 提供安装的方法
路由更新触发方式
一、主动触发
router-link绑定了click方法,触发history.push或者history.replace,从而触发history.transitionTo。(这里的history并不是H5的History对象,而是vue-router里定义的history对象)
transitionTo用于处理路由转换,其中包含了updateRoute用于更新_route。
在beforeCreate中有劫持_route的方法,当_route变化后,触发router-view的变化。
二、地址变化(如:在浏览器地址栏直接输入地址)
HashHistory和HTML5History会分别监控hashchange和popstate来对路由变化作对用的处理 。
HashHistory和HTML5History捕获到变化后会对应执行push或replace方法,从而调用transitionTo,剩下的就和上面主动触发一样啦。
路由更新详解
在使用单页面前端路由跳转时,提供了两种方式:Hash模式和History模式
那为什么这两种方式能够实现试图更新不跳转,其原因在于:
1、Hash模式:
hash(#)是URL 的锚点,代表的是网页中的一个位置,单单改变#后的部分,浏览器只会滚动到相应位置,不会重新加载网页,也就是说 #是用来指导浏览器动作的,对服务器端完全无用,HTTP请求中也不会不包括#;同时每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用”后退”按钮,就可以回到上一个位置。
(hash虽然出现在URL中,但不会被包括在HTTP请求中。它是用来指导浏览器动作的,对服务器端完全无用,因此,改变hash不会重新加载页面)
HashHistory.push()流程:$router.push() --> HashHistory.push() --> History.transitionTo() --> History.updateRoute() --> vm.render() (调用window.location.hash修改)
HashHistory.replace() 实现基本与push类似,不同之处在于,它并不是将新路由添加到浏览器访问历史的栈顶,而是调用window.location.replace替换掉当前的路由
另外,HashHistory通过setupListeners,设置监听了浏览器事件hashchange来应对用户直接在地址栏输入改变路由,调用的函数为replaceHash,即在浏览器地址栏中直接输入路由相当于代码调用了replace()方法
2、History模式:
HTML5 History API提供了一种功能,能让开发人员在不刷新整个页面的情况下修改站点的URL,就是利用 history.pushState API 来完成 URL 跳转而无须重新加载页面;
3、抽象模式
抽象模式是属于最简单的处理了,因为不涉及和浏览器地址相关记录关联在一起;整体流程依旧和 HashHistory 是一样的,只是这里通过数组来模拟浏览器历史记录堆栈信息。
Hash模式和History模式比较
除了#比较丑外,调用history.pushState()相比于直接修改hash的优势有:
- pushState设置的新URL可以是与当前URL同源的任意URL;而hash只可修改#后面的部分,故只可设置与当前同文档的URL
- pushState设置的新URL可以与当前URL一模一样,这样也会把记录添加到栈中;而hash设置的新值必须与原来不一样才会触发记录添加到栈中
- pushState通过stateObject可以添加任意类型的数据到记录中;而hash只可添加短字符串
- pushState可额外设置title属性供后续使用
问题? 因为hash模式仅改变hash部分的内容,而hash部分是不会包含在HTTP请求中的:
http://oursite.com/#/user/id //
如重新请求只会发送http://oursite.com/
故在hash模式下遇到根据URL请求页面的情况不会有问题。
而history模式则会将URL修改得就和正常请求后端的URL一样http://oursite.com/user/id
会报404error,这个时候需要vue应用覆盖所有情况给出一个404页面或者是node作后台的话中间过滤一层。
匹配优先级
有时候,同一个路径可以匹配多个路由,此时,匹配的优先级就按照路由的定义顺序:谁先定义的,谁的优先级就最高
关于守卫**
参数或查询的改变并不会触发进入/离开的导航守卫。不会应用在跳转路由上,而仅仅应用在其目标上。为redirect前的/a路由添加一个beforeEach或beforeLeave守卫并不会有任何效果。
全局守卫beforeEach 当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于等待中。
全局解析守卫beforeResolve 和router.beforeEach类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
全局后置钩子afterEach和守卫不同的是,这些钩子不会接受next函数也不会改变导航本身。
路由独享的守卫beforeEnter 与全局前置守卫的方法参数是一样的,在路由配置上直接定义,直接写在路由配置里面。
组件内的守卫 beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
完整流程:
1.导航被触发
2.在失活的组件里调用离开守卫
3.调用全局的beforeEach守卫
4.在重用的组件里调用beforeRouteUpdate守卫
5.在路由配置里调用beforeEnter
6.解析异步路由组件
7.在被激活的组件里调用beforeRouteEnter
8.调用全局的beforeResolve守卫
9.导航被确认
10.调用全局的afterEach钩子
11.触发 DOM 更新
12.用创建好的实例调用beforeRouteEnter守卫中传给next的回调函数。
比较Vue-router与React-router
他们最基本的初衷都是实现前端路由。所谓前端路由,简单来说,就是当浏览器的url产生变化时,不向服务器进行请求,而是直接控制前端页面产生变化,以期待前端在比如功能切换时,产生类似页面跳转等效果。
而这里面最基本的,无论是vue-router还是react-router,都要提供一种配置方式,让使用者可以配置出url路径和要展示的组件的对应关系。让其在用户更改url时通过这个url找到对应的组件,从而有针对性的在页面上渲染。
需要注意的是:VUE的路由配置要提供给new VueRouter()对象,这个对象要在全局VUE对象初始化时提供;而React路由则需要配置给全局
总结不同:
vue-router是全局配置方式,react-router是全局组件方式。
vue-router仅支持对象形式的配置,react-router支持对象形式和JSX语法的组件形式配置。
vue-router任何路由组件都会被渲染到
位置,react-router子组件作为children被传入父组件,而根组件被渲染到 位置。
参考
原理分析
源码详细分析