路由概念,路由模式,vue-router 管理路由与渲染得实现......
一 路由概念与路由模式
1. 路由概念
后端路由(传统路由)
传统的 Web 应用都是由多个页面组成,页面之间通过链接的方式进行跳转。
每切换一个页面,就由浏览器发起页面请求,后端服务器接收到请求,对这个地址进行匹配,找到这个地址对应的逻辑,解析地址及参数,最终返回这个地址对应的页面 HTML。
这个对请求地址进行匹配的逻辑就被称为路由,它是在后端发生的,因此也叫后端路由
。
对后端路由来说,每一个页面对于前端代码来说都是全新的,前端代码在页面加载的时候开始运行,在页面关闭或者跳到下一个页面的时候结束运行。
前端路由
前端路由的概念是随着单页面应用(Single Page Application)的流行而产生的。
单页面应用,是指整个 Web 应用只有一个页面。由浏览器发起请求再由后端返回页面 HTML 这样的过程只会在第一次访问时发生。在第一次请求完毕后,后面的页面逻辑都由前端进行控制。
这样做的好处是一旦用户完成第一次页面加载,在后续的使用中,用户都不会看到页面加载的过程,从而获得更流畅的使用体验。不好的地方在于,失去了 Web 应用最核心的特性 URL
。
一个典型的前端路由在单页面场景下大概需要关注以下几个问题:
(1)定义路由表,即各种 URL 分别对应哪些逻辑(一般来说就是对应界面的渲染)。
(2)获取当前访问的 URL,并根据路由表匹配中对应的逻辑并调用它(渲染对应的界面)。
(3)处理链接跳转,如果链接地址是在单页面应用的范围内,则不能使用浏览器导航,而是直接完成新 URL 对应的界面的渲染,并将浏览器中显示的 URL 更新为新界面对应的 URL。
(4)监视 URL 的变更,当用户手工更改 URL 或者有其它逻辑更改了 URL 之后,需要重新进行路由匹配并完成界面的渲染。
一般来说,(1)是纯计算逻辑,不需要什么特别的处理,(2)可以由 location 这个 API 进行获取,因此前端路由中值得关注的核心问题主要就是 (3)和 (4),简单地归纳就是更新浏览器 URL 和监视浏览器 URL 改变。
2. 路由模式
hash 模式
对于一个 URL 如:/home#/hello/world
,其中的 hash 部分就是#/hello/world
。
当我们在单页面应用中切换到另一个页面时,修改 hash 即可。
hash 部分在浏览器导航的时候并不会被传给后端服务器,也可以方便地用 JavaScript 修改,并且修改它时也不会发生重新导航的情况,因此对于单页面应用来说,非常适合用来作为前端路由的方案。
对于 hash 模式下 URL 的监听:
- 老旧的浏览器,使用定时器,定时获取浏览器的 URL,并与之前的结果比对
- 较新的浏览器提供了 hashchange 事件,直接监听这个事件即可
- 更新的浏览器提供了 popstate 事件
hash 模式的缺点:
- 不符合用户的固有认知,也不太美观
- hash 部分不会被传递给后端服务器,导致没有办法进行服务端渲染,进而影响搜索引擎的收录
history 模式
在单页面应用下,这个模式的核心在于 history.pushState(state, title, url) 这个 API,它的含义是向浏览器的历史栈(即前进后退的栈)中压入一个新的状态,从逻辑上相当于跳转到了一个新的页面,但是并不真的重新加载或重新导航。
使用这个 API 很方便地修改浏览器中的 URL,并正确地处理前进 / 后退的问题。
该模式下对 URL 的监听使用 popstate 事件。当用户进行导航动作(前进 / 后退等)或有 history.back()、history.forward() 之类的调用时,popstate 事件就会发生。
3. 占位组件
Vue-Router 的作用不仅是管理路由,还需要配合 Vue 完成路由对应界面的渲染,Vue 本身是声明式渲染的,而 Vue-Router 通过声明组件(<router-view>
)的方式来接管渲染。
当开发者使用 Vue-Router 时,<router-view>
组件会被全局注册,但它并没有具体的内容可渲染,当渲染到 <router-view>
时,就会由 Vue-Router 来决定这个组件的位置应该渲染哪个界面,从而实现从 URL 到路由匹配再到渲染对应界面的过程。
二 Vue-Router 实现逻辑
1. 插件安装
Vue.use() 是 Vue 提供的用来安装插件的方法,它要求参数提供一个 install() 方法,Vue 会调用这个 install() 方法完成安装。vue-router 的 install() 方法位于一个单独的文件,即 src/install.js。
install() 方法主要做了这么几件事情:
- 声明了 beforeCreate() 和 destroyed() 两个 mixin,这样在 Vue 实例的生命周期中能够处理 vue-router 相关的逻辑。
- 声明了两个属性 <math xmlns="http://www.w3.org/1998/Math/MathML"> r o u t e r 和 router 和 </math>router和route,分别指向了 this._routerRoot 对象上的_router 和_route。
- 将_route 变成响应式数据,这样当它变更的时候就会触发组件的重新渲染。
- 声明了两个全局组件 RouterView 和 RouterLink,这正是我们经常使用的
<router-view>
和<router-link>
。
2. 定义路由
声明 VueRouter 类,代码位于 src/index.js。new VueRouter() 时调用。
初始化时主要有这样几件事情:
- 创建了用于进行 URL 匹配的路由表。路由表用来存储定义好的路由与对应的页面组件(用于在
<router-view>
中渲染)的关系。 - 根据 mode 配置项决定使用 hash 模式还是 history 模式。
- 根据对应的模式,选择负责管理历史记录和 URL 的 History 子类,初始化后赋值给 this.history。
VueRouter 类中 通过this.matcher = createMatcher(options.routes || [], this)
创建路由表。createMatcher
方法源码位于 src/create-matcher.js 。
使用名称或 URL 匹配路由表中定义好的路由,参数解析(如解析 /foo/:bar),子路由处理,别名 alias 处理,都是通过 createMatcher
处理的。最后会返回Route对象:
javascript
const route: Route = {
name: location.name || (record && record.name),
meta: (record && record.meta) || {},
path: location.path || '/',
hash: location.hash || '',
query,
params: location.params || {},
fullPath: getFullPath(location, stringifyQuery),
matched: record ? formatMatch(record) : []
}
值得一说的是,传入一个路由的时候,路由会被编译成一个正则表达式进行参数解析(rc/create-matcher.js/matchRoute)。如传入一个路由 /foo/:bar:
javascript
const keys = [];
const regexp = pathToRegexp("/foo/:bar", keys);
// regexp = /^\/foo(?:\/([^\/#\?]+?))[\/#\?]?$/i
// keys = [{ name: 'bar', prefix: '/', suffix: '', pattern: '[^\\/#\\?]+?', modifier: '' }]
如果有参数则将参数从匹配的结果中取出,最终放到 Route 对象中的 params 属性中。
3. 路由模式的实现
路由模式的处理源码在 src/history 目录下,hash 模式的处理逻辑在 hash.js, history 模式在 html5.js中,两个类的接口很相似。
路由模式的实现主要任务点在,更新浏览器 URL(ensureURL 方法) 和监视浏览器 URL 改变(setupListeners 方法)。
处理完之后回到VueRouter 类的 init 方法修改 _route, _route 是一个响应式数据,当它发生变更的时候,组件会重新渲染。
4. 全局组件 RouterView 和 RouterLink
源码位于 src/components/*.js
RouterView 的作用就是将当前匹配的路由的组件渲染出来,因为当前是哪个组件是会动态变化的,因此 Vue-Router 选择了使用 render() 方法来实现。首先从路由中取出对应的组件,然后使用 h() 方法(即 createElement() 方法)返回组件的虚拟 DOM,后续跟 Vue 中的组件渲染一样。
RouterLink 主要是对链接的事件做了拦截,当点击链接的时候,会尝试调用 router.push() 或者 router.replace() 方法来完成导航,并阻止浏览器默认的导航,从而使这些链接也变成前端路由接管。
参考:TooooBug 、 Vue Router