1. vue.use方法源码
在这一步,我们先来看看Vue.use()方法是干嘛用的,这对于手写VueRouter至关重要
路径:node_modules/vue/src/core/global-api/use.ts
import type { GlobalAPI } from 'types/global-api'
import { toArray, isFunction } from '../util/index'
export function initUse(Vue: GlobalAPI) {
Vue.use = function (plugin: Function | any) {
const installedPlugins =
this._installedPlugins || (this._installedPlugins = [])
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// additional parameters
const args = toArray(arguments, 1)
args.unshift(this)
if (isFunction(plugin.install)) {
plugin.install.apply(plugin, args)
} else if (isFunction(plugin)) {
plugin.apply(null, args)
}
installedPlugins.push(plugin)
return this
}
}
1. Vue.use干嘛用的?
一般是用来注册 第三方的库用的,里面可以接受函数、对象。如果接受函数就直接调用,如果接收的是对象,就会去里面找install方法,再去调用他
2. 判断插件是否注册
……上面也暂时删除
export function initUse(Vue: GlobalAPI) {
Vue.use = function (plugin: Function | any) {
const installedPlugins =
this._installedPlugins || (this._installedPlugins = [])
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
……中间部分暂时删去
installedPlugins.push(plugin)
return this
}
}
- use方法,接受参数plugin,值可以是函数也可以是任意类型。
- 里面的installedPlugins是一个数组,是库的集合
- 判断要注册的这个库,是否已经存在了,利用indexOf方法进行判断,如果已经存在了,就直接return this,this在Vue.use方法里面,所以this指向的是Vue实例
- 如果这个库不存在,就插入到installedPlugins这个数组里面去
3. use如何处理传递进来的参数
import type { GlobalAPI } from 'types/global-api'
import { toArray, isFunction } from '../util/index'
export function initUse(Vue: GlobalAPI) {
Vue.use = function (plugin: Function | any) {
const installedPlugins =
this._installedPlugins || (this._installedPlugins = [])
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// additional parameters
+ const args = toArray(arguments, 1)
+ args.unshift(this)
+ if (isFunction(plugin.install)) {
+ plugin.install.apply(plugin, args)
+ } else if (isFunction(plugin)) {
+ plugin.apply(null, args)
+ }
installedPlugins.push(plugin)
return this
}
}
- 把传递进来的参数转化为数组,去掉第一个参数
- 往开头又插入一个参数,就是this,就是Vue实例。
这一点很重要
,因为创建VueRouter类的时候,第一个参数就是Vue实例,我们可以挂载$router对象,而Vue实例就是这里传递进来的。 - 判断plugin.install是不是函数,如果是,就利用apply方法来进行调用,this指向plugin
- 如果不是函数,就直接调用plugin
2. 小tips
看源码,一定要抓住核心,不然就会迷失
vue-router文件
/src/index.js文件
里面有hash和html5,表示两种路由模式
base.js表示公共语法
abstract.js后续才会用到
3. VueRouter类实现
第一,用vue/cli脚手架创建一个项目
vue create router-demo
第二,修改里面的router/index.js里面的一句代码
// 1. 引入vue
import Vue from 'vue'
// 2. 引入 vue-router
// import Router from 'vue-router'
+import Router from '../my-router/index'
// 3. Vue注册路由实例
Vue.use(Router)
因此,也需要创建和router文件夹并列的文件夹 my-router/index.js
里面写我们写下来的代码
第三步,需要如下文件,如图,依次创建
第四步,在写代码时,可以时不时去瞄一下,官方的vue-router实例,里面的src文件夹下的代码是怎么写的,看不懂也没关系的啦,不用死扣
第五步,my-router/index.js文件,创建一个VueRouter类
// 1. 类
class VueRouter {
constructor(options) {
// 2. 接受options.routes 里面是路由的规则
}
// 3. init方法 传入Vue 实例 传入路由变量的实例 后面能够进行方法的调用
init(Vue) {
console.log(Vue);
}
}
上面的options是干嘛用的呢?
我们在router/index.js里面,new Vue实例时,就会传入一个对象, 就给了上面的options
第六步,我们给VueRouter类添加
// 1. VueRouter类
class VueRouter {
constructor(options) {
// 2. 接受options.routes 里面是路由的规则 我们把routes这个数字里面的路由规则给到this._routes私有变量
+ this._routes = options.routes || []
}
// 3. init方法 传入Vue 实例 传入路由变量的实例
init(Vue) {
console.log(Vue);
}
}
4. install方法的使用
第一步,在my-router/install.js文件里面
创建一个全局变量,导出一个install方法。
导出一个_Vue变量,赋值为Vue,Vue是Vue.use(VueRouter)时,会调用install方法,然后传递进来的 Vue实例
// 4.导出Vue 让在其他文件也可以使用Vue实例
export let _Vue
// 5. 导出install方法
export default function install(Vue) {
// 5.1 获取Vue的实例
_Vue = Vue
}
第二步,我们调用Vue.mixin({})方法,里面使用beforeCreate方法
// 4.导出Vue 让在其他文件也可以使用Vue实例
export let _Vue
// 5. 导出install方法
export default function install(Vue) {
// 5.1 获取Vue的实例
_Vue = Vue
// 5.2 进行vue实例的混入
+ _Vue.mixin({
// 混入后 所有的vue实例的beforeCreate()方法都会执行如下代码
+ beforeCreate() {
}
})
}
第三步,增加逻辑判断,是不是new Vue实例呢?如果是的话 就能够获取到$router属性
我们做了几件事情,
- 挂载this,
- router赋值给一个私有属性 this._router
- 通过私有属性
this._router
进行调用init方法
【还记得他吗,上一节,我们创建VueRouter类时,使用的】。因为this.$options.router接受到的是VueRouter的实例,实例自然有init方法
// 4.导出Vue 让在其他文件也可以使用Vue实例
export let _Vue
// 5. 导出install方法
export default function install(Vue) {
// 5.1 获取Vue的实例
_Vue = Vue
// 5.2 进行vue实例的混入
_Vue.mixin({
// 混入后 所有的vue实例的beforeCreate()方法都会执行如下代码
beforeCreate() {
+ // 5.3 判断是否为Vue的实例
// 如果条件成立 是Vue的实例 【这里的Vue实例指的是 new Vue()】
+ if (this.$options.router) {
// 源码发现,Vue的实例会挂载到当前的私有属性_routerRoot属性上 方便后面的组件可以使用
+ this._routerRoot = this
// 挂载路由实例到_router私有属性上
// 方便后面的组件可以使用
+ this._router = this.$options.router
// 调用router实例的 init方法 router是new VueRouter()上,因此会有init方法
+ this._router.init(this)
}
}
})
}
这里所指的new Vue传递Vue实例,指的是什么?是main.js文件里面的这个
我们去查看VueRouter源码里面的install.js文件,发现很多相似之处
第六步,如果不是new Vue 怎么办呢
就获取父亲【APP.VUE】的_routerRoot 也就是this,this._routerRoot = this.$parent && this.$parent._routerRoot
这句代码就是把父组件App.vue的this值给到当前的组件实例的_routerRoot
_Vue.mixin({
// 混入后 所有的vue实例的beforeCreate()方法都会执行如下代码
beforeCreate() {
// 5.3 判断是否为Vue的实例
// 如果条件成立 是Vue的实例 【这里的Vue实例指的是 new Vue()】
if (this.$options.router) {
// 源码发现,Vue的实例会挂载到当前的私有属性_routerRoot属性上 方便后面的组件可以使用
this._routerRoot = this
// 挂载路由实例到_router私有属性上
// 方便后面的组件可以使用
this._router = this.$options.router
// 调用router实例的 init方法 router是new VueRouter()上,因此会有init方法
this._router.init(this)
} else {
// 这句代码是什么意思?
// 5.4 否则,就是其他对应的组件
// this.$parent表示父组件App.vue this.$parent._routerRoot 访问App.vue组件的_routerRoot的私有属性 这个App.vue组件的创建是new Vue()而来的 他的_routerRoot属性 有this 就是Vue实例,每个组件就能够使用this了
+ this._routerRoot = this.$parent && this.$parent._routerRoot
}
}
})
如果报错
这里察觉到Vue.use(Router)里面的Router是undefined
说明 install方法出了问题:
- 检查install方法的逻辑
- 其实最开始就该检查install方法是否引入成功 不然也不会是undefiend的了
5. 组件创建
第一步,components/下面创建两个文件,link.js和view.js分别用来渲染 router-link和router-view组件
第二步,link.js里面这样写
导出渲染一个a标签,感到陌生的是,domProps
这个写法,这里还是有点不太清楚,!待查,注意书写是一个{domProps: {再一个对象}}
// 9. 这里创建的是一个a标签 domProps是属性名,第三个坑表示内容
// 默认导出
// props传参 + render渲染组件
export default {
props: {
to: {
type: String,
required: true
}
},
render(h) {
return h('a', { domProps: {href: '#' + this.to}}, [this.$slots.default])
}
}
// 10. 我们修改router/index.js里面引入的Router类
// 启动项目即可
// 此时点击 标签 发现组件内容不能够切换
第三步,view.js里面这样写
默认导出一个对象,里面用render函数
渲染一个div
,内容是router-view
// 8. 这里创建的是router-view组件 渲染为一个div 内容我们先填充为router-view
export default {
render(h) {
return h('div', 'router-view')
}
}
第四步,install.js里面引入注册
// 导入link和view 进行组件的注册
+import Link from './components/link'
+import View from './components/view'
// 4.导出Vue 让在其他文件也可以使用Vue实例
export let _Vue
// 5. 导出install方法
export default function install(Vue) {
// 5.1 获取Vue的实例
_Vue = Vue
// 5.2 进行vue实例的混入
_Vue.mixin({
// 混入后 所有的vue实例的beforeCreate()方法都会执行如下代码
beforeCreate() {
// 5.3 判断是否为Vue的实例
// 如果条件成立 是Vue的实例 【这里的Vue实例指的是 new Vue()】
if (this.$options.router) {
// 源码发现,Vue的实例会挂载到当前的私有属性_routerRoot属性上 方便后面的组件可以使用
this._routerRoot = this
// 挂载路由实例到_router私有属性上
// 方便后面的组件可以使用
this._router = this.$options.router
// 调用router实例的 init方法 router是new VueRouter()上,因此会有init方法
this._router.init(this)
} else {
// 这句代码是什么意思?
// 5.4 否则,就是其他对应的组件
// this.$parent表示父组件App.vue this.$parent._routerRoot 访问App.vue组件的_routerRoot的私有属性 这个App.vue组件的创建是new Vue()而来的 他的_routerRoot属性 有this
this._routerRoot = this.$parent && this.$parent._routerRoot
}
}
})
// 注册组件
+ Vue.component('RouterView', View)
+ Vue.component('RouterLink', Link)
}
我们把install.js
文件引入到index.js
挂载VueRouter
类上面去
import install from './install'
import createMatcher from './create-matcher'
// 1. VueRouter类
class VueRouter {
constructor(options) {
// 2. 接受options.routes 里面是路由的规则
this._routes = options.routes || []
}
// 3. init方法 传入Vue 实例 传入路由变量的实例
init(Vue) {
console.log(Vue);
}
}
// 6. 把install方法 挂载到VueRouter实例上面去
+VueRouter.install = install
+export default VueRouter
6. createMatcher方法的使用
第一步,在index.js里面引入文件,使用方法。
这个方法用来“解析routes选项,匹配路由规则”
import install from './install'
+import createMatcher from './create-matcher'
// 1. VueRouter类
class VueRouter {
constructor(options) {
// 2. 接受options.routes 里面是路由的规则
this._routes = options.routes || []
// 11. 添加对createMatch方法的调用
+ this.matcher = createMatcher(this._routes)
}
// 3. init方法 传入Vue 实例 传入路由变量的实例
init(Vue) {
console.log(Vue);
}
}
// 6. 把install方法 挂载到VueRouter实例上面去
VueRouter.install = install
export default VueRouter
第二步,在create-matcher.js
文件里面写如下方法
- 引入createRouteMap方法
- 从该方法里面 按需导入 pathList pathMap这两个方法
- 里面声明match 和 addRoutes方法 => match用来匹配路由规则,addRoutes用来动态添加路由规则
// 12. 导入createMap方法
import createRouteMap from './create-route-map'
export default function createMatcher(routes) {
// debugger
// 从 createRouteMap 里面导出 pathList pathMap的方法
const {pathList, pathMap} = createRouteMap(routes)
// match方法
function match() {}
// addRoutes方法 动态添加路由
function addRoutes() {
createRouteMap(routes, pathList, pathMap)
}
// 返回
return {
match,
addRoutes
}
}
第三步,我们可以去查看 官方的 create-matcher.js文件里面的方法的使用
也有match 和 addRoute,同时addRoutes里面也用了createRouteMap方法
第四步,我们去create-route-map.js文件里面声明这个方法
- 参数
routes: 路由规则,是一个数字,里面记录了path, component,children
oldPathList: 数组, [/home, /home/list] 大概是这样
oldPathMap: 是一个对象,{‘/home‘: {}}
- 内部声明变量,接受形式参数
- 解析routes,遍历,再调用
addRouteRecord
这个方法。逻辑是,如果动态添加路由调用 addRoute => createRouteMap => addRouteRecord【这个方法用来添加路由记录,我们下面还要添加】 - 返回pathList和pathMap 我们可以打印这看看。
export default function createRouteMap(routes, oldPathList, oldPathMap) {
// debugger
// 声明初始值 pathList 和 pathMap
const pathList = oldPathList || []
const pathMap = oldPathMap || {}
// 遍历 routes 调用addRouteRecord()方法
// 对路由进行解析 还要考虑 children的情况
routes.forEach((route) => {
addRouteRecord(route, pathList, pathMap)
})
console.log(pathList);
console.log(pathMap);
return {
pathList,
pathMap
}
}
第五步,创建 addRouteRecord 方法,用来添加路由规则
四个参数,前面三个和之前的类似。
第四个,表示,当前的组件是否有父亲的路由记录?有的要也要进行设置,没有就是undefined
function addRouteRecord(route, pathList, pathMap, parentRecord) {
}
判断是否有父亲,有的话 path的值会不同
function addRouteRecord(route, pathList, pathMap, parentRecord) {
+ const path = parentRecord ? `${parentRecord.path}/${route.path}` : route.path
}
进行record记录的拼凑
function addRouteRecord(route, pathList, pathMap, parentRecord) {
const path = parentRecord ? `${parentRecord.path}/${route.path}` : route.path
+ const record = {
path,
component: route.components,
// 记录了父子的关系
parent: parentRecord
}
}
判断pathMap里面是否已经有这个记录了,如果有了,就跳过
如果没有,就要加入到pathMap里面去
判断route里面是否有children属性,如果有的话,就要递归遍历,添加记录,
// 如果已经有了record 相同的path 就直接跳过
+ if (!pathMap[path]) {
pathList.push(path)
pathMap[path] = record
}
// 判断路由中是否有子路由
+ if (route.children) {
route.children.forEach((childRoute) => {
addRouteRecord(childRoute, pathList, pathMap, record)
})
}
也许看到这个你就理解了
小结:
先是创建了VueRouter实例 index.js里面
先是创建了VueRouter实例
,然后声明init方法,并且用_routes属性接受传递进来的routes参数。
-
全局混入,让每个组件都可以使用Vue的实例
install.js文件里面,我们进行了
全局混入,让每个组件都可以使用Vue的实例
this【this._routerRoot = this】。我们还调用了init方法。
-
渲染组件
我们在my-router/view.js和link.js里面创建了a标签和div组件,
引入到了install.js文件,并利用了Vue.component组件进行注册。
再把install.js文件引入到index.js文件,挂载到Vue实例上去
-
我们在index.js里面调用了一个
createMatcher方法
,要去声明。这个方法从createRoute方法里面解构出pathList和pathMap两个变量。
并且声明,match和addRoutes方法,返回他们
-
解析路由规则,添加路由记录
我们在createRouteMap方法里面,遍历每个routes选项,为每个路由选项添加记录record如果有children,还要递归遍历,进行添加,
7. 我们测试一下
第一步,确保你在,项目根目录的router/index.js里面引入了刚刚自己写的my-router/index.js文件
第二步,在项目的views文件夹下面,创建三个组件,并且router/index.js里面进行路由的配置 routes
const routes = [
{
path: '/about',
components: About,
children: [{
path: 'users',
components: Users
}]
},
{
path: '/',
components: Home,
},
]
第三,App.vue里面设置好router-link组件和router-view组件
<template>
<div id="app">
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</nav>
<router-view />
</div>
</template>
<style lang="less">
</style>
第四,打开页面
点击a链接,发现地址栏的信息能够改变,只是组件内容不会切换
8. match方法的实现
第一,根据path => 从pathMap中找到对应的路由记录信息。pathMap里面是path和组件相关的信息
const record = pathMap[path]
第二,判断是否有record信息,如果有的话,就调用 createRoute方法,传入record信息和path信息
第三,在 my-router/util/route.js
文件里面定义这个方法 createRoute。
- 方法返回 match数组 和 path路径。
- 通过遍历判断record是否有parent,有的话unshift到match数组里,match数组记录了[parentRecord, childRecord]信息,与路由组件渲染内容一一对应
export default function createRoute(record, path) {
// [parentRecord, childRecord] 这是match数组存储记录的顺序,先parent 再child => 组件的渲染的顺序是UI有
// 路由记录信息的数组
const match = []
// 路由记录放到数组,可能有parent,也放到数组 注意是unshift
while (record) {
match.unshift(record)
record = record.parent
}
// 返回path和match数组结合的对象
return {
path,
match
}
}
第四,match方法里面,如果有record路由记录,就return createRoute(record, path)的结果,如果没有,就返回createRoute(null, path)的结果
export default function createMatcher(routes) {
// 从 createRouteMap 里面导出 pathList pathMap的方法
const {pathList, pathMap} = createRouteMap(routes)
// 疑问:这个方法什么时候调用呢 ?
+ function match(path) {
// 根据路径 从 pathMap里面找到对应的路由信息
+ const record = pathMap[path]
+ if (record) {
// 根据对应的路由信息,调用 createRoute方法 最终生成 数组【有可能包含了父路由和子路由的record信息】
// 这个方法 就让我们根据path => 直接获取到包含整个 路由记录的数组
// 后续利用这个数组里面的记录的信息的内容,能够渲染出组件内容
+ return createRoute(record, path)
+ }
+ return createRoute(null, path)
+ }
// addRoutes方法 动态添加路由
function addRoutes(routes) {
createRouteMap(routes, pathList, pathMap)
}
// 返回
return {
match,
addRoutes
}
}
9. 历史记录处理
在这里,我们声明父类公共部分,和hash文件的类,
在base文件里设置路由跳转
的方法,
在hash
文件里面设置:1. 获取路由地址 2. 添加 'hashchange'事件的监听
第一步,在history
文件夹下面,创建 base.js
hash.js
html5.js
文件
第二步,写base.js文件
- 引入createRoute方法,我们依靠它, 给current变量赋值,current记录了path和match的信息
- 写路由跳转的方法,修改current,通过 match方法,获取到
record
+path再给createRoute方法【在match方法里面执行】 - 执行相应的回调
import createRoute from "../util/route";
export default class History {
// router赋值
constructor(router) {
this.router = router
// 为什么是null 和 '/'
// 因为是初始化的时候,路由记录还没什么信息?
// current调用
this.current = createRoute(null, '/')
}
// 调用路由跳转的方法
transitionTo(path, onComplete) {
// 能够返回一个路由规则对象
this.current = this.router.matcher.match(path)
console.log(this.current);
onComplete && onComplete()
}
}
第三步,hash.js
文件写
-
类要通过 extends实现继承 super关键字把 router给到父类
-
ensureSlash方法执行,要先声明这个方法 => 首次进入页面添加 '/'
-
声明getCurrentLocation方法和setUpListener方法
- window.location.hash.slice(1)能够获取到 #后面的值 www.baidu.com/#/login 获取到 /login,而slice(1) 能够获取到 login
- setUpListener监听 hashchange事件,调用ensureSlash,如果有hash,就会return;执行跳转,hash事件触发,就会要跳转
/*
getCurrentLocation: 获取当前的地址栏地址 没有参数
setUpListener: 能够监听地址栏的改变 hashchange
ensureSlash:确保首次进入页面能够地址加上 / 加了后 自动拼接为 /#/ 也就是说 #/是自动拼上去的
*/
import History from "./base"
export default class HashHistory extends History {
constructor(router) {
// 这个super必须写
super(router)
// 确保首次访问加上 #
ensureSlash()
}
// 获取当前的地址栏的地址
getCurrentLocation() {
// 为什么要slice(1) 因为 -> 要省略#
// window.location.hash -> 获取的是 #后面的内容
return window.location.hash.slice(1)
}
// 只要监听setUpListener方法 window就会开始监听hashchange事件
// 有什么参数? 不用传递
setUpListener() {
window.addEventListener('hashchange', () => {
ensureSlash()
this.transitionTo(this.getCurrentLocation())
})
}
}
// 确保地址栏输入信息时,能够加上 / 只要你加上 / 后面会拼接为 /#/
function ensureSlash() {
// 如果单击的是链接 肯定有地址 不用再拼
if (window.location.hash) {
return
}
// 如果是首次进入 没有地址 就要手动拼
return window.location.hash = '/'
}
10. 不同的路由模式的处理
第一步, my-router/index.js
-
声明mode值,以及 this.mode都为 options.mode或者是 hash[默认值]
-
通过switch进行匹配,
new HashHistory 和 HTML5History
实例 ,记得引入哈 -
获取history实例,调用history.transitioTo方法进行跳转
- 参数1:通过history.getCurrentLocation方法获取path
- 把setUpListener传递作为回调函数
class VueRouter {
constructor(options) {
// 2. 接受options.routes 里面是路由的规则
this._routes = options.routes || []
// 11. 添加对createMatch方法的调用
this.matcher = createMatcher(this._routes)
// mode的修改
// 注意mode的值来自于options
+ const mode = (this.mode = options.mode || 'hash')
+ switch(this.mode) {
+ case('hash'):
+ // 传递this就是VueRouter实例
+ this.history = new HashHistory(this)
+ break
+ case('history'):
+ this.history = new HTML5History(this)
+ break
+ default:
+ throw new Error('mode error')
+ }
+ }
// 3. init方法 传入Vue 实例 传入路由变量的实例
init() {
+ const history = this.history
+ const setUpListener = () => {
+ history.setUpListener()
}
// 因为 new HashHistory(this) 时 会执行一次ensureSlash,修改了地址 -> 因此要进行一次路由的跳转 -> 要执行一次 transitionTo()方法
// history.getCurrentLocation() 获取当前的地址栏输入的地址
// setUpListener执行回调函数 注意 这里别加括号
+ history.transitionTo(
+ history.getCurrentLocation(),
+ setUpListener
+ )
}
}
11. 测试
在base.js
文件里面打印即可看到结果
import createRoute from "../util/route";
export default class History {
// router赋值
constructor(router) {
this.router = router
// 为什么是null 和 '/'
// 因为是初始化的时候,路由记录还没什么信息?
// current调用
this.current = createRoute(null, '/')
}
// 调用路由跳转的方法
transitionTo(path, onComplete) {
// 能够返回一个路由规则对象
this.current = this.router.matcher.match(path)
+ console.log(this.current);
onComplete && onComplete()
}
}
12. 定义响应式路由属性
这一步,目的是定义route属性,能够在路由跳转的时候修改route属性
第一步,在install.js文件,定义响应式属性 _route
// 导入link和view 进行组件的注册
import Link from './components/link'
import View from './components/view'
// 4.导出Vue 让在其他文件也可以使用Vue实例
export let _Vue
// 5. 导出install方法
export default function install(Vue) {
// 5.1 获取Vue的实例
_Vue = Vue
// 5.2 进行vue实例的混入
_Vue.mixin({
// 混入后 所有的vue实例的beforeCreate()方法都会执行如下代码
beforeCreate() {
// 5.3 判断是否为Vue的实例
// 如果条件成立 是Vue的实例 【这里的Vue实例指的是 new Vue()】
if (this.$options.router) {
// 源码发现,Vue的实例会挂载到当前的私有属性_routerRoot属性上 方便后面的组件可以使用
this._routerRoot = this
// 挂载路由实例到_router私有属性上
// 方便后面的组件可以使用
this._router = this.$options.router
// 调用router实例的 init方法 router是new VueRouter()上,因此会有init方法
this._router.init(this)
// 响应式属性 地址栏属性变化 对应的属性也要发生改变
+ Vue.util.defineReactive(this, "_route", this._router.history.current)
} else {
// 这句代码是什么意思?
// 5.4 否则,就是其他对应的组件
// this.$parent表示父组件App.vue this.$parent._routerRoot 访问App.vue组件的_routerRoot的私有属性 这个App.vue组件的创建是new Vue()而来的 他的_routerRoot属性 有this
this._routerRoot = this.$parent && this.$parent._routerRoot
}
Object.defineProperty(Vue.prototype, '$router', {
get() {
return this._routerRoot._router
}
})
Object.defineProperty(Vue.prototype, '_route', {
get() {
// 这个值 app._route = route
return this._routerRoot._route
}
})
}
})
// 注册组件
Vue.component('RouterView', View)
Vue.component('RouterLink', Link)
}
第二步,在history/base.js文件里面定义 cb属性,和listen方法
import createRoute from "../util/route";
export default class History {
// router赋值
constructor(router) {
this.router = router
// 为什么是null 和 '/'
// 因为是初始化的时候,路由记录还没什么信息?
// current调用
this.current = createRoute(null, '/')
+ this.cb = null
}
+ listen(cb) {
+ // 注意 这里的cb 会被赋值为函数
// 所以下面的transitioTo会被调用 传递参数 this.current
this.cb = cb
}
// 调用路由跳转的方法
transitionTo(path, onComplete) {
// 能够返回一个路由规则对象
this.current = this.router.matcher.match(path)
// console.log(this.current);
onComplete && onComplete()
}
}
第三步,在index.js里面调用上面定义的方法listen
import install from './install'
import createMatcher from './create-matcher'
import HashHistory from './history/hash'
import HTML5History from './history/html5'
// 1. VueRouter类
class VueRouter {
constructor(options) {
// 2. 接受options.routes 里面是路由的规则
this._routes = options.routes || []
// 11. 添加对createMatch方法的调用
this.matcher = createMatcher(this._routes)
// mode的修改
// 注意mode的值来自于options
const mode = (this.mode = options.mode || 'hash')
switch(mode) {
case('hash'):
// 传递this就是VueRouter实例
this.history = new HashHistory(this)
break
case('history'):
this.history = new HTML5History(this)
break
default:
throw new Error('mode error')
}
}
// 3. init方法 传入Vue 实例 传入路由变量的实例
init(app) {
const history = this.history
const setUpListener = () => {
history.setUpListener()
}
// 因为 new HashHistory(this) 时 会执行一次ensureSlash,修改了地址 -> 因此要进行一次路由的跳转 -> 要执行一次 transitionTo()方法
// history.getCurrentLocation() 获取当前的地址栏输入的地址
// setUpListener执行回调函数 注意 这里别加括号
history.transitionTo(
history.getCurrentLocation(),
setUpListener
)
// 在这里调用 history里面的listen方法
// 箭头函数传递给listen
+ history.listen((route) => {
+ app._route = route
+ })
}
}
// 6. 把install方法 挂载到VueRouter实例上面去
VueRouter.install = install
export default VueRouter
第四步,调用cb函数,如果地址栏信息改变,route信息也要改变
在哪里调用cb函数呢?
import createRoute from "../util/route";
export default class History {
// router赋值
constructor(router) {
this.router = router
// 为什么是null 和 '/'
// 因为是初始化的时候,路由记录还没什么信息?
// current调用
this.current = createRoute(null, '/')
this.cb = null
}
listen(cb) {
// 注意 这里的cb 会被赋值为函数
// 所以下面的transitioTo会被调用 传递参数 this.current
this.cb = cb
}
// 调用路由跳转的方法
transitionTo(path, onComplete) {
// 能够返回一个路由规则对象
this.current = this.router.matcher.match(path)
// console.log(this.current);
// 调用cb -> 是啥意思
+ this.cb && this.cb(this.current)
onComplete && onComplete()
}
}
是什么意思呢?我下面截了个图。要记得 _route属性是我们之前定义的“响应式属性”挂载到vue实例上哒
this.current里面记录了路由的规则对象信息,我们赋值给route
8/22日小结:
-
学习了vue2响应式和vue3响应式的简单写法
-
学习了发布订阅模式和观察者模式的概念
-
学习了vueRouter的,match方法是干嘛用的
-
学习了vueRouter是处理历史记录:
- 如果第一次进入页面,会给用户的地址增加 ‘/’
- 有方法 能够获取到当前地址栏的地址
- 如果地址栏的信息改变,hashChange事件能够触发
- 能够实现路由跳转
-
不同的路由模式处理
- 根据mode,匹配不同的路由模式
- 第一次进入页面 触发init方法
- 后续改变地址栏,触发hashChange
-
测试 打印current变量
-
响应式路由:
- 定义_route变量,并且定义cb函数,来修改cb变量
\