路由的作用:根据不同的路径映射到不同的视图。
Vue-Router的能力十分强大,它支持hash、history、abstract3中路由方式,提供了<router-link>和<router-view>2种组件,还提供了简单路由配置和一系列好用的API。
~~~
<!-- 使用 router-link 组件来导航. -->
<!-- 通过传入 `to` 属性指定链接. -->
<!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
~~~
~~~
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
~~~
~~~
import Vue from 'vue'
import VueRouter from 'vue-router'
import App from './App'
Vue.use(VueRouter)
~~~
~~~
// 1. 定义(路由)组件。
// 可以从其他文件 import 进来
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
~~~
~~~
// 2. 定义路由
// 每个路由应该映射一个组件。 其中"component" 可以是
// 通过 Vue.extend() 创建的组件构造器,
// 或者,只是一个组件配置对象。
// 我们晚点再讨论嵌套路由。
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]
~~~
~~~
// 3. 创建 router 实例,然后传 `routes` 配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
routes // (缩写)相当于 routes: routes
})
~~~
~~~
// 4. 创建和挂载根实例。
// 记得要通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
const app = new Vue({
el: '#app',
render(h) {
return h(App)
},
router
})
~~~
## 路由注册
Vue从它的设计上就是一个渐进式Javascript框架,它本身的核心是解决视图渲染的问题,其他能力就通过插件的方式来解决。
Vue-Router是官方维护的路由插件。
### Vue.use
Vue提供了Vue.use的全局API来注册这些插件,接受了一个plugin参数。每一个插件都需要实现一个静态的install方法,当我们只需Vue.use注册插件的时候,就会执行这个install方法,第一个参数是Vue对象。
### 路由安装
当用户执行Vue.use(VueRouter)的时候,实际上就是执行install函数,且确保install逻辑只执行一次。
Vue-Router安装最重要的一步就是利用**Vue.mixin把beforeCreate和destroyed钩子函数注入到每一个组件中**。
~~~
export function initMixin (Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
this.options = mergeOptions(this.options, mixin)
return this
}
}
~~~
实现实际非常简单,**就是把药混入的对象通过mergeOptions合并到Vue的options中**。**由于每个组件的构造函数都会在extend阶段合并Vue.options到自身的options中,所以也就相当于每个组件都定义了mixin定义的选项**。
Vue-Router的install方法:
* 先看混入的beforeCreate构造函数,**this._router表示VueRouter实例本身**,在newVue时传入的;
* 执行了this._router.init()方法**初始化router**;
* 然后用defineReactive方法吧**this._route变成响应式对象**。
* 接着给Vue原型上定义了$router和$route2个属性的get方法,这就是为什么在组件实例上**可以访问this.$router以及this.$route**。
* 接着又通过Vue.component方法定义了全局的<router-link>和<router-view>2个组件,因此可以直接使用。
* 最后定义了路由中的钩子函数的合并策略,和普通的钩子函数一样。
### 总结
Vue编写插件的时候通常要提供静态的install方法,我们通过`Vue.use(plugin)`时候,就是执行install方法。
Vue-Router的install方法会给每个组件注册beforeCreate和destoryed钩子函数。在`beforeCreate`做一些私有属性定义和路由初始化工作。
## VueRouter对象
VueRouter的实现是一个类,我们先对它做一个简单分析。
构造函数定义了一些属性,其中this.app表示根Vue实例,
this.apps保存持有$options.router属性的Vue实例
this.options保存传入的路由配置
this.beforeHooks,this.resolveHooks,this.afterHooks表示一些钩子函数
**this.matcher表示路由匹配器。**
this.fallback表示浏览器不支持history.pushStart,根据传入的fallback配置参数,决定是否回退到hash模式
**this.mode**表示路由创建模式(history,hash,abstract)
**this.history**表示路由历史的具体的实现实例。
~~~
this.mode = mode
switch (mode) {
case 'history':
this.history = new HTML5History(this, options.base)
break
case 'hash':
this.history = new HashHistory(this, options.base, this.fallback)
break
case 'abstract':
this.history = new AbstractHistory(this, options.base)
break
default:
if (process.env.NODE_ENV !== 'production') {
assert(false, `invalid mode: ${mode}`)
}
}
~~~
实例化VueRouter后会返回它的实例router,**我们在new Vue的时候会把router作为配置的属性传入**。
~~~
new Vue({
el: '#root',
router,
template: '<App/>',
components: { App }
})
~~~
上一节讲beforeCreate混入的时候代码
~~~
beforeCreate() {
if (isDef(this.$options.router)) {
// ...
this._router = this.$options.router
this._router.init(this)
// ...
}
}
~~~
所以组件在执行beforeCreate钩子函数的时候,**如果传入了router实例,都会执行router.int方法**。
init很简单:传入参数是Vue实例,然后存储到this.apps中;只有根Vue实例会保存到this.app中,并且**会拿到this.history,根据它的不同类型来执行不同逻辑**。平时使用的hash路由多一些。
this.matcher.match方法去做匹配。
### 总结
组件的初始化阶段,**执行到beforeCreate钩子函数的时候,会执行router.init方法**,然后又会执行history.transitionTo方法做**路由过渡**,进而引出了**matcher**的概念。
`this.$router.push({ path: `/GoodsDetails/${goodsId}`});`
~~~
flag:this.$route.params.flag==undefined?0:this.$route.params.flag,
orderId:this.$route.params.orderId==undefined?null:this.$route.params.orderId,
~~~
## matcher
~~~
xport type Matcher = {
match: (raw: RawLocation, current?: Route, redirectedFrom?: Location) => Route;
addRoutes: (routes: Array<RouteConfig>) => void;
};
~~~
Matcher返回2个方法,**match和addRoutes**。
路由中重要的2个概念:**Location和Route**。
* Location
~~~
declare type Location = {
_normalized?: boolean;
name?: string;
path?: string;
hash?: string;
query?: Dictionary<string>;
params?: Dictionary<string>;
append?: boolean;
replace?: boolean;
}
~~~
Vue-Router中定义的Location数据结构和浏览器提供的window.location部分结构有点类似,他们都是对url的结构化描述
`/abc?foo=bar&baz=qux#hello`,它的`path`是`/abc`,`query`是`{foo:'bar',baz:'qux'}`。
* Route
~~~
declare type Route = {
path: string;
name: ?string;
hash: string;
query: Dictionary<string>;
params: Dictionary<string>;
fullPath: string;
matched: Array<RouteRecord>;
redirectedFrom?: string;
meta?: any;
}
~~~
Route表示的是路由中的一条线路,**除了描述类似Location的path、query、hash等,还有matched表示匹配到所有的RouteRecord**。
### createMatcher
2个参数,一个是router(new VueRouter返回的实例),**一个是routes,是用户定义的路由配置**。
~~~
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]
~~~
createMatcher:首先要**创建一个路由映射表**。把用户的路由配置转换成一张路由映射表,包含3个部分,
* pathList存储的所有path,是**为了记录路由配置中所有的path**。
patch表示一个patch到RouteRecord的映射关系,**通过path能快速查到对应的RouteRecord**。
nameMap表示那么到RouteRecord的映射关系。**通过name能快速查到对应的RouteRecord**。
RouteRecord:遍历routes为每一个route生成一条记录(我们配置的时候会配置子路由),所以整个**RouteRecord也就是一个树形结构**。通过深度遍历,可以拿到要给route下的完整记录。
**matcher是一个对象,它对外暴露了match和addRoutes方法**。
### addRoutes
addRoutes的作用是**动态添加路由配置**,因为时间开发中有些场景时不能把路由写死的,**需要根据一些条件动态添加路由**。
addRoutes方法十分简单,再次调用creatRouteMap即可,传入新的routes配置,由于patchList、pathMap,namPath都是引用类型,执行addRoutes后会修改他们的值。
### match
match方法接收3个参数,raw是RawLocation类型,它可以是一个url字符串,也可以是一个Location对象,currentRoute是Route类型,它表示当前路径,
**match方法返回的是一个路径,它的作用是根据传入的raw和当前的路径currentRoute计算出一个新的路径并返回**。
~~~
function match (
raw: RawLocation,
currentRoute?: Route,
redirectedFrom?: Location
): Route
~~~
根据raw,current(currentRoute)计算出新的location,它主要处理了raw的两种情况,一种是params且没有path,一种是有path,
对于第一种情况,如果current有name,则计算出location也有name
计算出新的location后,对location的那么和path有两种情况
* name
有name的情况,根据nameMap匹配到recored,它就是一个RouterRecord对象,如果record不存在,则匹配失败。
然后拿到record对应的paramNames,再对比currentRoute中的params,把交集部分添加到location中,然后通过fillParams方法根据record.path和location.path计算出location.path。最后生成一套新路径。
* path
通过name我们可以很快的找到record,但是通过path并不能,因为计算后的location.path是一个真实路径,而record的path可能会有param,因此需要对所有的pathList做顺序遍历,然后通过matchRoute(根据record.regex,location.path,location.params)方法匹配,去生成一条心路径。
因为是顺序遍历,所以书写路由配置要注意路径的顺序,因为前面的会优先尝试匹配。
createRoute可以根据record和location创建出来,最终返回的是一套Route路径,在Vue-Router中,所有的Route最终都会通过createRoute函数创建,并且它最后是不可以被外部修改的。
### 总结
我们了解了Location、Route、RouteRecord等概念,并通过**matcher的mach方法**,我们会**找到匹配的路径Route,这个对Route的切换**,组件的渲染都非常有意义。
## 路径切换
history.transitionTo是Vue-Router中非常重要的方法,**当我们切换路由线路是,就会执行该方法**。
matcher,如何找到匹配的新线路。匹配到新线路后,又做了什么?
transitionTo根据**目标location和当前路径this.current**执行this.router.match方法去**匹配到目标的路径**。this.current是history维护的当前路径。
拿到新的路径后,那么接下来就会执行confirmTransition方法去做真正的切换,由于这个过程可能有一些**异步操作(异步组件)**,会有成**功回调函数和失败回调函数**。
### 导航守卫
实际就是发生在**路由路径切换的时候,执行的一系列钩子函数**。
顺序
1. 在失活的组件里调用离开守卫
2. 调用全局的beforeEach守卫
3. 在重用的组件里调用beforeRouteUpdate守卫
4. 在激活的路由配置里调用beforeEneter
5. 解析异步路由组件。
路由切换除了执行这些钩子函数,从表象上有2个地方发生改变,**一个是url发生变化,一个是组件发生变化**。
### url
当我们点击router-link的时候,实际上最终会执行`router.push`
~~~
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
this.history.push(location, onComplete, onAbort)
}
~~~
`push`函数会先执行`this.transitionTo`做路径切换,在切换完成的回调函数中,执行`pushHash`函数:
~~~
function pushHash (path) {
if (supportsPushState) {
pushState(getUrl(path))
} else {
window.location.hash = path
}
}
~~~
`pushState`会调用浏览器原生的`history`的`pushState`接口或者`replaceState`接口,**更新浏览器的 url 地址,并把当前 url 压入历史栈中**。
然后在`history`的初始化中,会设置一个**监听器,监听历史栈的变化**:
当点击浏览器**返回按钮**的时候,如果已经有 url 被压入历史栈,则会触发`popstate`事件,然后拿到当前要跳转的`hash`,执行`transtionTo`方法做一次路径转换。
在使用Vue-Router开发项目的时候,打开调试页面http://localhost:8080后**自动把url修改为http://localhost:8080/#/**?
在实例化HashHistory的时候,这是path为空,所以执行**replaceHash('/' + path)**,然后内部会执行一次getUrl,计算出来新的url为http://localhost:8080/#/,这就是url改变的原因。
### 组件
路由最终的渲染离不开组件,Vue-Router内置了<router-view>组件,`<router-view>`是一个`functional`组件,**它的渲染也是依赖`render`函数**
渲染什么组件呢?
首先获取当前的路径
~~~
const route = parent.$route
~~~
我们给Vue的原型上定义了$route
~~~
Object.defineProperty(Vue.prototype, '$route', {
get () { return this._routerRoot._route }
})
~~~
**$route定义在Vue.prototype上,每个组件实例访问$route属性,就是访问根实例的_route,也就是当前的路由线路**。
根据当前线路匹配的路径和depth找到对应的RouteRecord,进而找到该渲染的组件。
render函数的最后根据component渲染出对弈的组件vnode。
由于我们把跟**Vue实例的$route属性定义成响应式的**,我们在每个<route-router>执行render函数的时候,都会**访问parent.$route,触发了getter**,相当于**<route-router>对它有依赖**,然后在执行完transitionTo后,修改app._route的时候,**又触发了setter**,因此会通知**<router-view>的渲染watcher**更新,**重新渲染组件**。
Vue-Router 还内置了另一个组件`<router-link>`, 它支持**用户在具有路由功能的应用中(点击)导航**。 通过`to`属性指定**目标地址**,默认渲染成带有正确链接的`<a>`标签,可以通**过配置`tag`属性生成别的标签**。另外,当目标路由成功激活时,链接元素自动设置一个**表示激活的 CSS 类名**。
`<router-link>`比起写死的`<a href="...">`会好一些,理由如下:
1. **无论是HTML5 history模式还是hash模式,它的表现行为一致**,所以当你要切换路由模式,或者IE9降级使用hash模式,无需做任何变动。
2. 在HTML5 history模式下,**router-link会守卫点击事件**,让浏览器不在重新加载页面。
3. 当你在HTML5 history模式下使用base选项之后,所有的**to属性都不需要写(基路径)了**。
`<router-link>`标签的渲染也是基于`render`函数,它首先做了路由解析:
### 总结
路径变化时路由中最重要的功能,我们要记住以下内容:
**路由始终会维护当前的路线,路由切换的时候把当前路线切换到目标路线,切换过程中会执行一系列的导航守卫钩子,会修改url,同样也会渲染对应的组件,切换完毕后会把目标线路更新替换当前线路,这样就会作为下一次的路径切换的依据**。
- 空白目录
- 双樾
- JS基础知识
- JS-WEB-API
- 开发环境
- 运行环境
- ES6
- 原型
- 异步
- 虚拟dom
- mvvm
- 组件化和React
- hybrid
- 其他
- 补充
- 技巧
- 快乐动起来呀
- css
- 掘金小册子
- js基础知识
- ES6知识点
- JS异步
- JS进阶知识
- 思考题
- DevTools Tips
- 浏览器基础知识
- 浏览器缓存机制0
- 浏览器渲染原理
- 安全防范知识点0
- 从V8中看JS性能优化0
- 性能优化琐碎事
- Webpack性能优化0
- 实现小型打包工具0
- React和Vue
- Vue生命周期
- vue基础知识点
- Vue响应式
- vue高级
- React基础
- Vue.js技术解密
- 准备工作
- 数据驱动
- new Vue()
- vue实例挂载
- 组件化
- 深入响应式原理
- 编译
- 扩展
- Vue Router
- Vuex