路由是什么
路由的定义,维基是这样定义的;路由(routing)就是通过互联的网络把信息从源地址传输到目的地址的活动。路由发生在OSI网络参考模型中的第三层即网络层。路由引导分组转送,经过一些中间的节点后,到它们最后的目的地。作成硬件的话,则称为路由器。路由通常根据路由表——一个存储到各个目的地的最佳路径的表——来引导分组转送 上面的定义可能很官方,但是我们可以抽出一些重点
路由是一种活动,负责将信息从源地址传输到目的地址; 要完成这样一个活动需要一个很重要的东西路由表-源地址和目标地址的映射表
前端路由是什么
其实前端路由是针对 spa说的,在spa出现之前,页面的跳转(导航)都是通过服务端控制的,并且跳转存在一个明显白屏跳转过程;spa出现后,为了更好的体验,就没有再让服务端控制跳转了,于是前端路由出现了,前端可以自由控制组件的渲染,来模拟页面跳转
前端路由的两种模式
- hash模式 hash 模式 ##a ##b 通过# 后边的路径方式进行切换 // window.location.hash = '/about' // window.onhashchange = function () {} // hash 变化渲染对应的路径组件
// window.history.pushState() // 实现增添路径 但是强制刷新还是有问题, (服务端来解决这个问题 // window.onpopstate = function () {} // 监控浏览器路径的变化
// vue-router 源码中 在hash模式下, 如果支持onpopstate 会优先使用,否则使用onhashchange
- history 模式
history 实现 history 提供了 pushState 和 replaceState 两个方法,这两个方法改变 URL 的 path 部分不会引起页面刷新 history 提供类似 hashchange 事件的 popstate 事件,但 popstate 事件有些不同:
通过浏览器前进后退改变 URL 时会触发 popstate 事件 通过pushState/replaceState或标签改变 URL 不会触发 popstate 事件。 好在我们可以拦截 pushState/replaceState的调用和标签的点击事件来检测 URL 变化 通过js 调用history的back,go,forward方法课触发该事件
所以监听 URL 变化可以实现,只是没有 hashchange 那么方便。
hash 和history模式的区别是什么
hash: 兼容所有浏览器,包括不支持 HTML5 History Api 的浏览器,例 www.abc.com/#/index,has… hash的改变会触发hashchange事件,通过监听hashchange事件来完成操作实现前端路由。hash值变化不会让浏览器向服务器请求。// 监听hash变化,点击浏览器的前进后退会触发
window.addEventListener('hashchange', function(event){
let newURL = event.newURL; // hash 改变后的新 url
let oldURL = event.oldURL; // hash 改变前的旧 url
},false)
history: 兼容能支持 HTML5 History Api 的浏览器,依赖HTML5 History API来实现前端路由。没有#,路由地址跟正常的url一样,但是初次访问或者刷新都会向服务器请求,如果没有请求到对应的资源就会返回404,所以路由地址匹配不到任何静态资源,则应该返回同一个index.html 页面,需要在nginx中配置。 abstract: 支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式。
实现思路
1. 要明白的几个概念
### 路由规则、配置对象(RouteConfig)
### 路由记录(RouteRecord)
### 路由对象(Route)
### 位置(Location)
### 路由组件(RouteComponent)
2. 主要的思路
从第一节的知识我门可以得知 要实现一个前端路由,需要三个部分 路由映射表: 一个能表达url和组件关系的映射表,可以使用Map、对象字面量来实现 匹配器: 负责在访问url时,进行匹配,找出对应的组件 历史记录栈: 浏览器平台,已经原生支持,无需实现,直接调用接口
3. 目录结构
4. 初始化的一些东西
1. Vue.use(VueRouter) install 方法
上面也提到vue-router的入口文件在src/index.js中,那我们去index.js中找找install方法
```js
// src/index.js
import { install } from './install' // 导入安装方法
// ...
// VueRouter类
export default class VueRouter {
// ...
}
// ...
VueRouter.install = install // 挂载安装方法,Vue.use时,自动调用install方法
VueRouter.version = '__VERSION__'
// 浏览器环境,自动安装VueRouter
if (inBrowser && window.Vue) {
window.Vue.use(VueRouter)
}
```
可以看到,开头导入了install方法,并将其做为静态方法直接挂载到VueRouter上,这样,在Vue.use(VueRouter)时,install方法就会被调用; 可以看到,如果在浏览器环境,并且通过script标签的形式引入Vue时(会在window上挂载Vue全局变量),会尝试自动使用VueRouter 我们接下来看看install.js中是什么
install方法
import View from './components/view'
import Link from './components/link'
export let _Vue // 用来避免将Vue做为依赖打包进来
// install方法
export function install (Vue) {
if (install.installed && _Vue === Vue) return // 避免重复安装
install.installed = true
_Vue = Vue // 保留Vue引用
const isDef = v => v !== undefined
// 为router-view组件关联路由组件
const registerInstance = (vm, callVal) => {
let i = vm.$options._parentVnode
// 调用vm.$options._parentVnode.data.registerRouteInstance方法
// 而这个方法只在router-view组件中存在,router-view组件定义在(../components/view.js @71行)
// 所以,如果vm的父节点为router-view,则为router-view关联当前vm,即将当前vm做为router-view的路由组件
if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
i(vm, callVal)
}
}
// 注册全局混入
Vue.mixin({
beforeCreate () {
// this === new Vue({router:router}) === Vue根实例
// 判断是否使用了vue-router插件
if (isDef(this.$options.router)) {
// 在Vue根实例上保存一些信息
this._routerRoot = this // 保存挂载VueRouter的Vue实例,此处为根实例
this._router = this.$options.router // 保存VueRouter实例,this.$options.router仅存在于Vue根实例上,其它Vue组件不包含此属性,所以下面的初始化,只会执行一次
// beforeCreate hook被触发时,调用
this._router.init(this) // 初始化VueRouter实例,并传入Vue根实例
// 响应式定义_route属性,保证_route发生变化时,组件(router-view)会重新渲染
Vue.util.defineReactive(this, '_route', this._router.history.current)
} else {
// 回溯查找_routerRoot
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
}
// 为router-view组件关联路由组件
registerInstance(this, this)
},
destroyed () {
// destroyed hook触发时,取消router-view和路由组件的关联
registerInstance(this)
}
})
// 在原型上注入$router、$route属性,方便快捷访问
Object.defineProperty(Vue.prototype, '$router', {
get () { return this._routerRoot._router }
})
Object.defineProperty(Vue.prototype, '$route', {
// 每个组件访问到的$route,其实最后访问的都是Vue根实例的_route
get () { return this._routerRoot._route }
})
// 注册router-view、router-link全局组件
Vue.component('RouterView', View)
Vue.component('RouterLink', Link)
const strats = Vue.config.optionMergeStrategies
// use the same hook merging strategy for route hooks
strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
}
2. 初始化流程 _init() 方法
import install from "./install"
import createMatcher from './create-matcher';
import HashHistory from "./history/hashHistory";
import BrowserHistory from "./history/browserHistory";
class VueRouter {
constructor (options) {
// 创建了一个匹配器 1. 匹配功能 match 2. 可以添加匹配, 动态路由添加 addRoutes 权限
this.matcher = createMatcher(options.routes || []) // 获取用户的整个配置
// 创建历史管理 (路由两种模式 hash 和浏览器API)
this.mode = options.mode || 'hash'
switch (this.mode) {
case 'hash':
this.history = new HashHistory(this) // this是当前的实例
break;
case 'history':
this.history = new BrowserHistory(this) // this 是当前的实例
break;
}
this.beforeHooks
}
match (location) { // 做一个中转 把 matcher中的match方法 提出来
return this.matcher.match(location)
}
init (app) { // 目前这个app 指代就是 最外层的 new Vue
// 需要根据用户的配置 作出一个映射表
// 需要根据当前路径 实现下一个页面跳转的逻辑
// 跳转的路径 会进行匹配操作 ,根据路径获取对应的记录
const history = this.history
// transitionTo 跳转逻辑 hash browser都有
// getCurrentLocation 获取当前路径 hash 和browser 实现不一样
// setupListener hash 监听
// 初始化时 都需要调用 更新_route 的方法来获取current更新
let setUpHashListener = () => {
history.setupListener()
}
history.transitionTo(history.getCurrentLocation(), setUpHashListener)
// this.current 变换就触发这个方法
history.listen((route) => {
app._route = route // 更新视图的操作, 当current变化 更新
})
}
beforeEach(fn) {
this.beforeHooks.push(fn)
}
}
VueRouter.install = install
export default VueRouter
接收RouterOptions
RouterOptions定义了VueRouter所能接收的所有选项;
我们重点关注一下下面的几个选项值
routes是路由配置规则列表,这个主要用来后续生成路由映射表的;
它是一个数组,每一项都是一个路由配置规则(RouteConfig),关于RouteConfig,可以回看术语那一节;
mode、fallback是跟路由模式相关的
后面会详细讲VueRouter的路由模式
属性赋初值
对一些属性赋予了初值,例如,对接收全局导航守卫(beforeEach、beforeResolve、afterEach)的数组做了初始化
创建matcher
通过createMatcher生成了matcher 这个matcher对象就是最初聊的匹配器,负责url匹配,它接收了routes和router实例;createMatcher里面不光创建了matcher,还创建了路由映射表RouteMap,我们后面细看
确定路由模式
三种路由模式我们后面细讲 现在只需要知道VueRouter是如何确定路由模式的 VueRouter会根据options.mode、options.fallback、supportsPushState、inBrowser来确定最终的路由模式 先确定fallback,fallback只有在用户设置了mode:history并且当前环境不支持pushState且用户主动声明了需要回退,此时fallback才为true 当fallback为true时会使用hash模式; 如果最后发现处于非浏览器环境,则会强制使用abstract模式
作者:光辉GuangHui 链接:juejin.cn/post/688052… 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
根据路由模式生成不同的History实例
后续看 histroy文件夹
3. 设置监听方法 setUpHashListener
setupListeners
上面说到,在初始化时,会根据history类型,调用transitionTo跳转到不同的初始页面 为什么要跳转初始页面?
因为在初始化时,url可能指向其它页面,此时需要调用getCurrentLocation方法,从当前url上解析出路由,然后跳转之
可以看到HTML5History类和HashHistory类调用transitionTo方法的参数不太一样
前者只传入了一个参数 后者传入了三个参数
我们看下transitionTo方法的方法签名