> For the complete documentation index, see [llms.txt](https://1425816423.gitbook.io/my-knowledge-base/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://1425816423.gitbook.io/my-knowledge-base/yuan-ma-jie-xi/vue-yuan-ma-jie-xi/vue-router-yuan-ma-jie-xi-todo.md).

# Vue router源码解析（todo）

## Vue-router源码解析

status: Draft tags: Vue, 源码解析 Created time: March 7, 2023 10:57 AM emoji: <https://vuejs.org/logo.svg>

## VueRouter类

### VueRouter

### 三种mode

## 实现导航

### 一次导航的完整步骤

vue-router一次完整的导航包含以下步骤：

1. 导航被触发。
2. 在失活的组件里调用 `beforeRouteLeave` 守卫。
3. 调用全局的 `beforeEach` 守卫。
4. 在重用的组件里调用 `beforeRouteUpdate` 守卫 (2.2+)。
5. 在路由配置里调用 `beforeEnter`。
6. 解析异步路由组件。
7. 在被激活的组件里调用 `beforeRouteEnter`。
8. 调用全局的 `beforeResolve` 守卫 (2.5+)。
9. 导航被确认。
10. 调用全局的 `afterEach` 钩子。
11. 触发 DOM 更新。
12. 调用 `beforeRouteEnter` 守卫中传给 `next` 的回调函数，创建好的组件实例会作为回调函数的参数传入。

### 实现push

前端浏览器跳转页面通常有两种方式，代码跳转和浏览器跳转，代码跳转包含调用`this.$router.push()` 和点击`<router-link>`组件，浏览器跳转则是点击浏览器特定按钮，例如前进和后退按钮进行跳转。

无论是代码跳转还是浏览器跳转，其最终都调用了transitionTo函数，下面以HTML5History模式为例。

```
export class HTML5History extends History {
	setupListeners () {
    if (this.listeners.length > 0) {
      return
    }

    const router = this.router
    const expectScroll = router.options.scrollBehavior
    const supportsScroll = supportsPushState && expectScroll

    if (supportsScroll) {
      this.listeners.push(setupScroll())
    }

    const handleRoutingEvent = () => {
      const current = this.current

      // Avoiding first `popstate` event dispatched in some browsers but first
      // history route not updated since async guard at the same time.
      const location = getLocation(this.base)
      if (this.current === START && location === this._startLocation) {
        return
      }

      this.transitionTo(location, route => {
        if (supportsScroll) {
          handleScroll(router, route, current, true)
        }
      })
    }
    window.addEventListener('popstate', handleRoutingEvent)
    this.listeners.push(() => {
      window.removeEventListener('popstate', handleRoutingEvent)
    })
  }

	push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    const { current: fromRoute } = this
    this.transitionTo(location, route => {
      pushState(cleanPath(this.base + route.fullPath))
      handleScroll(this.router, route, fromRoute, false)
      onComplete && onComplete(route)
    }, onAbort)
  }
}
```

#### transitionTo函数

transitionTo位于类History，它是HashHistory、HTML5History 、AbstractHistory的父类。

transitionTo会再调用confirmTransition 函数来确定是否跳转路由，并传递一个onComplete函数作为完成时的回调。

```jsx
export class History {
	transitionTo (
    location: RawLocation,
    onComplete?: Function,
    onAbort?: Function
  ) {
    let route
    // catch redirect option https://github.com/vuejs/vue-router/issues/3201
    try {
      route = this.router.match(location, this.current)
    } catch (e) {
      this.errorCbs.forEach(cb => {
        cb(e)
      })
      // Exception should still be thrown
      throw e
    }
    const prev = this.current
    this.confirmTransition(
      route,
      () => {
        this.updateRoute(route)
        onComplete && onComplete(route)
        this.ensureURL()
        this.router.afterHooks.forEach(hook => {
          hook && hook(route, prev)
        })

        // fire ready cbs once
        if (!this.ready) {
          this.ready = true
          this.readyCbs.forEach(cb => {
            cb(route)
          })
        }
      },
      err => {
        // ...
      }
    )
  }

confirmTransition (route: Route, onComplete: Function, onAbort?: Function) {
    const current = this.current
    this.pending = route
    const abort = err => {
      // changed after adding errors with
      // https://github.com/vuejs/vue-router/pull/3047 before that change,
      // redirect and aborted navigation would produce an err == null
      if (!isNavigationFailure(err) && isError(err)) {
        if (this.errorCbs.length) {
          this.errorCbs.forEach(cb => {
            cb(err)
          })
        } else {
          if (process.env.NODE_ENV !== 'production') {
            warn(false, 'uncaught error during route navigation:')
          }
          console.error(err)
        }
      }
      onAbort && onAbort(err)
    }
    const lastRouteIndex = route.matched.length - 1
    const lastCurrentIndex = current.matched.length - 1
    if (
      isSameRoute(route, current) &&
      // in the case the route map has been dynamically appended to
      lastRouteIndex === lastCurrentIndex &&
      route.matched[lastRouteIndex] === current.matched[lastCurrentIndex]
    ) {
      this.ensureURL()
      if (route.hash) {
        handleScroll(this.router, current, route, false)
      }
      return abort(createNavigationDuplicatedError(current, route))
    }

    const { updated, deactivated, activated } = resolveQueue(
      this.current.matched,
      route.matched
    )

    const queue: Array<?NavigationGuard> = [].concat(
      // in-component leave guards
      extractLeaveGuards(deactivated),
      // global before hooks
      this.router.beforeHooks,
      // in-component update hooks
      extractUpdateHooks(updated),
      // in-config enter guards
      activated.map(m => m.beforeEnter),
      // async components
      resolveAsyncComponents(activated)
    )

    const iterator = (hook: NavigationGuard, next) => {
      if (this.pending !== route) {
        return abort(createNavigationCancelledError(current, route))
      }
      try {
        hook(route, current, (to: any) => {
          if (to === false) {
            // next(false) -> abort navigation, ensure current URL
            this.ensureURL(true)
            abort(createNavigationAbortedError(current, route))
          } else if (isError(to)) {
            this.ensureURL(true)
            abort(to)
          } else if (
            typeof to === 'string' ||
            (typeof to === 'object' &&
              (typeof to.path === 'string' || typeof to.name === 'string'))
          ) {
            // next('/') or next({ path: '/' }) -> redirect
            abort(createNavigationRedirectedError(current, route))
            if (typeof to === 'object' && to.replace) {
              this.replace(to)
            } else {
              this.push(to)
            }
          } else {
            // confirm transition and pass on the value
            next(to)
          }
        })
      } catch (e) {
        abort(e)
      }
    }

    runQueue(queue, iterator, () => {
      // wait until async components are resolved before
      // extracting in-component enter guards
      const enterGuards = extractEnterGuards(activated)
      const queue = enterGuards.concat(this.router.resolveHooks)
      runQueue(queue, iterator, () => {
        if (this.pending !== route) {
          return abort(createNavigationCancelledError(current, route))
        }
        this.pending = null
        onComplete(route)
        if (this.router.app) {
          this.router.app.$nextTick(() => {
            handleRouteEntered(route)
          })
        }
      })
    })
  }

  updateRoute (route: Route) {
    this.current = route
    this.cb && this.cb(route)
  }
}

// runQueue函数的定义位于util/async文件下
export function runQueue (queue: Array<?NavigationGuard>, fn: Function, cb: Function) {
  const step = index => {
    if (index >= queue.length) {
      cb()
    } else {
      if (queue[index]) {
        fn(queue[index], () => {
          step(index + 1)
        })
      } else {
        step(index + 1)
      }
    }
  }
  step(0)
}
```

#### confirmTransition

`confirmTransition` 函数将所有要执行的函数存储到队列中，以确保执行顺序的正确。最初执行：

```jsx
const queue: Array<?NavigationGuard> = [].concat(
      // 组件内beforeRouteLeave守卫
      extractLeaveGuards(deactivated),
      // 全局 beforeEach守卫
      this.router.beforeHooks,
      // 组件内update守卫（组件重用时调用）
      extractUpdateHooks(updated),
      // router配置文件中的beforeEnter守卫
      activated.map(m => m.beforeEnter),
      // 解析异步组件
      resolveAsyncComponents(activated)
    }
```

runQueue 函数会按顺序将这些守卫函数传入iterator 函数中执行，当用户在导航守卫中调用next()调转其他路由时，又会重新调用push跳转到目标路由。

```jsx
hook(route, current, (to: any) => {
  if (to === false) {
    // next(false) -> abort navigation, ensure current URL
    this.ensureURL(true)
    abort(createNavigationAbortedError(current, route))
  } else if (isError(to)) {
    this.ensureURL(true)
    abort(to)
  } else if (
    typeof to === 'string' ||
    (typeof to === 'object' &&
      (typeof to.path === 'string' || typeof to.name === 'string'))
  ) {
    // next('/') or next({ path: '/' }) -> redirect
    abort(createNavigationRedirectedError(current, route))
    if (typeof to === 'object' && to.replace) {
      this.replace(to)
    } else {
      this.push(to)
    }
  } else {
    // confirm transition and pass on the value
    next(to)
  }
})
```

在confirmTransition函数中调用runQueue时，又传入了一个回调函数，以便queue执行完毕后执行。

```jsx
runQueue(queue, iterator, () => {
    // wait until async components are resolved before
    // extracting in-component enter guards
    const enterGuards = extractEnterGuards(activated)
    const queue = enterGuards.concat(this.router.resolveHooks)
    runQueue(queue, iterator, () => {
      if (this.pending !== route) {
        return abort(createNavigationCancelledError(current, route))
      }
      this.pending = null
      onComplete(route)
      if (this.router.app) {
        this.router.app.$nextTick(() => {
          handleRouteEntered(route)
        })
      }
    })
  })
```

在这个函数中又会以相同的方式执行

* 激活路由的beforeRouteEnter守卫
* 全局beforeResolve守卫

最后又调用了

```jsx
onComplete(route)
if (this.router.app) {
  this.router.app.$nextTick(() => {
    handleRouteEntered(route)
  })
}
```

这里的onComplete来自transitionTo函数：

```jsx
this.confirmTransition(
  route,
  () => {
    this.updateRoute(route)
    onComplete && onComplete(route)
    this.ensureURL()
    this.router.afterHooks.forEach(hook => {
      hook && hook(route, prev)
    })

    // fire ready cbs once
    if (!this.ready) {
      this.ready = true
      this.readyCbs.forEach(cb => {
        cb(route)
      })
    }
  },

// updateRoute
 updateRoute (route: Route) {
    this.current = route
    this.cb && this.cb(route)
  }
// onComplete 又来自最开始的HTML5History
export class HTML5History extends History {
	push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    const { current: fromRoute } = this
    this.transitionTo(location, route => {
      pushState(cleanPath(this.base + route.fullPath))
      handleScroll(this.router, route, fromRoute, false)
// 最后这里的onComplete 来自用户所传的onComplete 函数，即this.$router.push(routeObj,onCompelete)
      onComplete && onComplete(route)
    }, onAbort)
  }
}

```

updateRoute函数执行的过程中会调用`this.cb(route)` ，这里的cb函数来自前面VueRouter类的init方法中：

```jsx
history.listen(route => {
  this.apps.forEach(app => {
    app._route = route
  })
})
```

由于app.\_route是响应式的，改变app.\_route时就会导致页面重新渲染，也就是触发了DOM更新。

也就是说在这一过程中做了：

* 更新路由
* 执行用户onComplete函数
* 确定路由（ensureURL）
* 执行全局beforeEach守卫
* 触发 DOM 更新。

为什么`app._route = route`先执行，但是DOM却在后面才触发更新呢？这是因为Vue组件渲染是异步的，由一个异步调度系统控制。

最后，调用 `beforeRouteEnter` 守卫中传给 `next` 的回调函数，创建好的组件实例会作为回调函数的参数传入。

这里使用`nextTick`的原因也同样是因为要延迟到DOM更新后再执行。

```jsx
if (this.router.app) {
  this.router.app.$nextTick(() => {
    handleRouteEntered(route)
  })
}

// handleRouteEntered 位于util/route.js文件下
export function handleRouteEntered (route: Route) {
  for (let i = 0; i < route.matched.length; i++) {
    const record = route.matched[i]
    for (const name in record.instances) {
      const instance = record.instances[name]
      const cbs = record.enteredCbs[name]
      if (!instance || !cbs) continue
      delete record.enteredCbs[name]
      for (let i = 0; i < cbs.length; i++) {
        if (!instance._isBeingDestroyed) cbs[i](instance)
      }
    }
  }
}
```

至此，导航解析核心流程执行完毕：

1. 导航被触发。
2. 在失活的组件里调用 `beforeRouteLeave` 守卫。
3. 调用全局的 `beforeEach` 守卫。
4. 在重用的组件里调用 `beforeRouteUpdate` 守卫 (2.2+)。
5. 在路由配置里调用 `beforeEnter`。
6. 解析异步路由组件。
7. 在被激活的组件里调用 `beforeRouteEnter`。
8. 调用全局的 `beforeResolve` 守卫 (2.5+)。
9. 导航被确认。
10. 调用全局的 `afterEach` 钩子。
11. 触发 DOM 更新。
12. 调用 `beforeRouteEnter` 守卫中传给 `next` 的回调函数，创建好的组件实例会作为回调函数的参数传入。

### 监听浏览器事件

## 组件

### router-link

### router-link


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://1425816423.gitbook.io/my-knowledge-base/yuan-ma-jie-xi/vue-yuan-ma-jie-xi/vue-router-yuan-ma-jie-xi-todo.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
