# Abstract模式

# 实例化

跟其他模式一样,都是在new VueRouter的时候,通过判断传入的mode来判断最后初始化路由模式

this.history = new AbstractHistory(this, options.base)
成功
1

上述代码可以看出,初始化的时候,会创建一个AbstractHistory实例,将this和设定的基准路径base,我们找到AbstractHistory的定义

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

  constructor (router: Router, base: ?string) {
    super(router, base)
    // 路由的历史记录
    this.stack = []
    // 当前路由的索引
    this.index = -1
  }
}
成功
1
2
3
4
5
6
7
8
9
10
11
12

HTML5History一样,都是继承自History,并且在初始化的时候进行的调用,这里就不赘述History的实现了。

接下来创建了两个变量,用于记录历史记录和当前的路由索引

# 初始化

abstract模式下,初始化的时候没有做额外操作,只是加个了路由变化的回调。

# 常见方法

分析完实例化和初始化后,我们分析下我们工作中一些常见的方法

# push

pushVueRouter类中的的实现跟其他模式是一致的,这里只分析不同的this.history.push的实现

push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
  this.transitionTo(
    location,
    route => {
      // 舍去当前索引之后的历史记录,将新路由添加到栈顶
      this.stack = this.stack.slice(0, this.index + 1).concat(route)
      // 更新索引
      this.index++
      onComplete && onComplete(route)
    },
    onAbort
  )
}
成功
1
2
3
4
5
6
7
8
9
10
11
12
13

可以看到,执行push操作的时候,就是舍去当前索引之后的历史记录,将新路由添加到栈顶

有的同学可能有疑问,为什么不直接使用this.stack.push(route)方法添加呢?

这是因为,还存在go方法,当前索引可能不是栈的最顶端,当我们执行push操作后,所有在当前历史记录后面的记录都应该被忽略。

如果我们直接使用this.stack.push(route),那么会直接把新的路由添加到历史记录栈的尾部,而忽略了可能存在的需要删除的历史记录,这样就可能导致历史记录错乱。因此,这里使用this.stack = this.stack.slice(0, this.index + 1).concat(route)而非this.stack.push(route)

# replace

同理,replace方法的不同点也是在于this.history.replace的实现

replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
  this.transitionTo(
    location,
    route => {
      // 用新的记录替换当前记录,并舍去后续历史记录
      this.stack = this.stack.slice(0, this.index).concat(route)
      onComplete && onComplete(route)
    },
    onAbort
  )
}
成功
1
2
3
4
5
6
7
8
9
10
11

replace方法就很清晰了,直接用新的记录替换当前记录,并舍去后续历史记录

# go

pushVueRouter类中的的实现跟其他模式是一致的,这里只分析不同的this.history.go的实现

go (n: number) {
  const targetIndex = this.index + n
  // 目标索引超出范围,不予处理
  if (targetIndex < 0 || targetIndex >= this.stack.length) {
    return
  }
  // 获取指向路由
  const route = this.stack[targetIndex]
  // 跳转
  this.confirmTransition(
    route,
    () => {
      const prev = this.current
      this.index = targetIndex
      this.updateRoute(route)
      // 触发跳转成功回调
      this.router.afterHooks.forEach(hook => {
        hook && hook(route, prev)
      })
    },
    err => {
      if (isNavigationFailure(err, NavigationFailureType.duplicated)) {
        this.index = targetIndex
      }
    }
  )
}
成功
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

go的方法相比于pushreplace方法,找到对应路由后,没有使用transitionTo包裹,直接使用confirmTransition跳转,这是因为go是对历史记录进行操作,已经有对应的route对象,不需要重新匹配,只需要按照对应的route对象进行切换组件即可

# 总结

abstract模式被设计为在没有浏览器API支持的环境下使用(例如服务器端渲染、React Native等)。在这种模式下,路由的改变并不会像在浏览器环境中那样体现在URL的变化中。所以在该模式的源码中,没有这部分的操作。

abstract模式通过对路由历史记录栈和索引的管理,实现路由的切换

  • push方法,直接删除索引后续的历史记录,将新的路由添加到历史记录栈的顶端
  • replace方法,用新的记录替换当前记录,并舍去后续历史记录
  • go方法,找到历史记录中的route对象后,执行组件切换