vue-router源码:abstract.js

vue-router源码解析系列。这是第十篇。本篇介绍源码中的abstract.js,是vue-router在非浏览器环境如node提供的History子类实现,Histor类是路由跳转的核心类,在之前的博客中已有详细的解析。本系列解析的是官方git库中3.1.6的版本源码。

源码链接:abstract.js。源码里面用的是typescript,但是不影响阅读。

AbstractHistory是个比较特别的实现,因为它不是浏览器环境,所以它没有window.history,它不需要再去考虑popstate hashchange事件,不需要去管浏览器的地址栏,但是它需要自己管理路由历史记录。所在AbstractHistory里面,设置了stack数组来存储访问的路由对象,设置了一个index实例属性,来表示当前访问的路由位置。

解析如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/* @flow */

import type Router from '../index'
import { History } from './base'
import { NavigationDuplicated } from './errors'
import { isExtendedError } from '../util/warn'

export class AbstractHistory extends History {
index: number
stack: Array<Route>

constructor (router: Router, base: ?string) {
super(router, base)
this.stack = []
this.index = -1
}

push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
this.transitionTo(
location,
route => {
// 注意这里是slice(0, this.index+1)调用
// 所以[0, this.index]这个区间的元素都被slice出来了,然后拼接一个route
// 得到一个新数组作为浏览器的历史记录数组
// 这个逻辑处理方式,跟浏览器对历史记录的管理是相似的
this.stack = this.stack.slice(0, this.index + 1).concat(route)
this.index++
onComplete && onComplete(route)
},
onAbort
)
}

replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
this.transitionTo(
location,
route => {
// 注意这里是slice(0, this.index)调用
// this.index这个位置的元素不会被包含在slice的返回结果里面
// 所以这样就能达到把this.index的元素替换为route
// 但是另一方面的话,this.index+1 这个位置开始之后的所有Route对象就都被丢弃了
this.stack = this.stack.slice(0, this.index).concat(route)
onComplete && onComplete(route)
},
onAbort
)
}

go (n: number) {
const targetIndex = this.index + n
if (targetIndex < 0 || targetIndex >= this.stack.length) {
return
}
const route = this.stack[targetIndex]
this.confirmTransition(
route,
() => {
this.index = targetIndex
this.updateRoute(route)
},
err => {
if (isExtendedError(NavigationDuplicated, err)) {
this.index = targetIndex
}
}
)
}

// 这个函数在AbstractHistory中被实现了
// 但是在vue-router的其它源码中看不到对它的调用
getCurrentLocation () {
const current = this.stack[this.stack.length - 1]
return current ? current.fullPath : '/'
}

// 这个函数没必要实现了,因为非浏览器环境,没有地址栏需要去关注
ensureURL () {
// noop
}
}

go的处理

不同于Html5History HashHistory可以直接使用window.history.go来实现go这个方法,AbstractHistory必须自己来实现go方法。不过看go的代码,不太明白一点:为什么不直接用transitionTo这个统一的路由跳转入口,而是要使用更加底层的confirmTranstion方法。

这行代码:

1
2
3
if (isExtendedError(NavigationDuplicated, err)) {
this.index = targetIndex
}

找到这行代码的添加的原因了: bug fixissue

不过go里面的处理有一点是对的,就是不改变stack数组的内容,只调整访问的指针index。这是与浏览器历史记录管理的特点相符的。曾经写过一篇跟浏览器记录访问相关的文章,可以帮助了解这一点:理解浏览器的历史记录

replace处理

replace方法看起来对于历史记录的管理,跟浏览器的管理方式不同,就是它会删除掉当前记录之后的记录,而不是仅仅替换当前记录,这一点在注释中有说明。

后记

之前对AbstractHistory的作用不是很明确,看了几个issue,发现这个类实际上可以在浏览器环境中使用的,只不过用它的话,就会完全脱离浏览器地址以及浏览器历史记录,感觉适合在一个没有去掉地址栏、没有前进后退按钮的网页环境中运行,比如开发一个桌面web应用程序的时候,所以这个类是有价值的,不能仅仅把它看成是node中才能使用。

不过对于这个类的理解还是存在两个不解的点:

  1. replace处理方式
  2. go方法内为啥不用transitionTo