Skip to content
给每一条河每一座山取一个温暖的名字,我有一所房子,面朝大海,春暖花开。

Vue-Router 源码摘要

初始化安装 install

ts
const router: Router = {
  install(app: App) {
    const router = this
    // 注册全局组件
    app.component('RouterLink', RouterLink)
    app.component('RouterView', RouterView)
    app.config.globalProperties.$router = router
    Object.defineProperty(app.config.globalProperties, '$route', {
      enumerable: true,
      get: () => unref(currentRoute),
    })
    const reactiveRoute = {}
    for (const key in START_LOCATION_NORMALIZED) {
      reactiveRoute[key] = computed(() => currentRoute.value[key])
    }
    // 数据注入
    app.provide(..., ...)
    // 卸载
    const unmountApp = app.unmount
    installedApps.add(app)
    app.unmount = function () {
      installedApps.delete(app)
      unmountApp()
    }
  }
}
return router

创建路由 createRouter

ts
 createRouter(options) {
  const matcher = createRouterMatcher(options.routes, options) {
    // 优化排序后的匹配项数组
    const matchers: RouteRecordMatcher[] = []
    const matcherMap = new Map()
    function getRecordMatcher(name: RouteRecordName) {
      return matcherMap.get(name)
    }
    function addRoute(...) { /* createRouterMatcher/addRoute */ }
    routes.forEach(route => addRoute(route))
    return { addRoute, resolve, removeRoute, getRoutes, getRecordMatcher }
  }
  const routerHistory = options.history
  const beforeGuards = useCallbacks<NavigationGuardWithThis<undefined>>()
  const beforeResolveGuards = useCallbacks<NavigationGuardWithThis<undefined>>()
  const afterGuards = useCallbacks<NavigationHookAfter>()
  const currentRoute = shallowRef<RouteLocationNormalizedLoaded>(
    START_LOCATION_NORMALIZED
  )
  // 改变路由
  function push(to: RouteLocationRaw) {
    return pushWithRedirect(to)
  }
  function pushWithRedirect(to, redirectedFrom) {
    const targetLocation: RouteLocation = (pendingLocation = resolve(to))
    const shouldRedirect = handleRedirectRecord(targetLocation)
    if (shouldRedirect) pushWithRedirect(...)
    let failure: NavigationFailure | void | undefined
    return (failure ? Promise.resolve(failure) : navigate(toLocation, from)).then(() => {
      // 执行 全局钩子 afterEach
      triggerAfterEach(to, from, failure) {
        for (const guard of afterGuards.list()) guard(to, from, failure)
      }
    })
  }
  const go = (delta: number) => routerHistory.go(delta)

  const installedApps = new Set<App>()
  const router: Router = {
    currentRoute,
    addRoute,
    beforeEach: beforeGuards.add,
    beforeResolve: beforeResolveGuards.add,
    afterEach: afterGuards.add,
    install(app: App) { /* 如下 */}
  }
  return router
}

添加匹配 createRouterMatcher/addRoute

ts
function addRoute(
  record: RouteRecordRaw,
  parent?: RouteRecordMatcher,
  originalRecord?: RouteRecordMatcher
) {
  const mainNormalizedRecord = normalizeRouteRecord(record) {
    return {
      path: record.path,
      redirect: record.redirect,
      name: record.name,
      meta: record.meta || {},
      ...
    }
  }
  const normalizedRecords = [mainNormalizedRecord]
  let matcher: RouteRecordMatcher
  for (const normalizedRecord of normalizedRecords) {
    matcher = createRouteRecordMatcher(normalizedRecord, parent, options) {
      // normalizedRecord => record
      const parser = tokensToParser(tokenizePath(record.path), options)
      const matcher: RouteRecordMatcher = assign(parser, {
        record,
        parent,
        children: [],
        alias: [],
      })
      return matcher
    }
    if (mainNormalizedRecord.children) {
      const children = mainNormalizedRecord.children
      for (let i = 0; i < children.length; i++) {
        addRoute(
          children[i],
          matcher,
          originalRecord && originalRecord.children[i]
        )
      }
    }
    originalRecord = originalRecord || matcher
    if(...) {
      insertMatcher(matcher) {
        let i = 0
        while(i < matchers.length && ...) {
          i++
        }
        matchers.splice(i, 0, matcher)
        // 缓存记录
        if (matcher.record.name && !isAliasRecord(matcher)) {
          matcherMap.set(matcher.record.name, matcher)
        }
      }
    }
  }
}

触发导航、执行守卫钩子 navigate

官网文档:守卫钩子

钩子分为 3 种:

  1. 全局钩子:beforeEach、beforeResolve、afterEach
  2. 配置文件内钩子:beforeEnter
  3. 组件内钩子:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave

钩子执行顺序示意图:

导航push => pushWithRedirect => navigate => 失活组件beforeRouteLeave => 全局beforeEach => 重用组件beforeRouteUpdate => 路由文件beforeEnter => 解析异步路由组件 => 激活组件beforeRouteEnter => 全局beforeResolve => 导航被确认 => 全局afterEach => 触发DOM更新 => beforeRouteEnter中回调函数 next(vm)

ts
function navigate(to, from) {
  let guards: Lazy<any>[];
  const [leavingRecords, updatingRecords, enteringRecords] =
    extractChangingRecords(to, from);
  // 提取 组件内钩子 beforeRouteLeave
  guards = extractComponentsGuards(
    leavingRecords.reverse(),
    "beforeRouteLeave",
    to,
    from
  );
  // 提取 setup 状态下钩子 beforeRouteLeave
  for (const record of leavingRecords) {
    record.leaveGuards.forEach((guard) => {
      guards.push(guardToPromiseFn(guard, to, from));
    });
  }
  // 导航取消确认 机制
  const canceledNavigationCheck = checkCanceledNavigationAndReject.bind(
    null,
    to,
    from
  );
  guards.push(canceledNavigationCheck);
  return runGuardQueue(guards)
    .then(() => {
      // 解析、执行 全局钩子 beforeEach
      guards = [];
      for (const guard of beforeGuards.list()) {
        guards.push(guardToPromiseFn(guard, to, from));
      }
      guards.push(canceledNavigationCheck);
      return runGuardQueue(guards);
    })
    .then(() => {
      // 解析、执行重用 组件内钩子 beforeRouteUpdate
    })
    .then(() => {
      // 解析、执行 配置文件钩子 beforeEnter
      guards = [];
      for (const record of to.matched) {
        if (record.beforeEnter && !from.matched.includes(record)) {
          if (isArray(record.beforeEnter)) {
            for (const beforeEnter of record.beforeEnter)
              guards.push(guardToPromiseFn(beforeEnter, to, from));
          } else {
            guards.push(guardToPromiseFn(record.beforeEnter, to, from));
          }
        }
      }
      guards.push(canceledNavigationCheck);
      return runGuardQueue(guards);
    })
    .then(() => {
      // 解析、执行 组件内钩子 beforeRouteEnter
    })
    .then(() => {
      // 解析、执行 全局钩子 beforeResolve
    });
}
function runGuardQueue(guards: Lazy<any>[]): Promise<void> {
  return guards.reduce(
    (promise, guard) => promise.then(() => guard()),
    Promise.resolve()
  );
}

路由视图 RouterView

ts
export const RouterView = RouterViewImpl;
export const RouterViewImpl = /*#__PURE__*/ defineComponent({
  name: "RouterView",
  props: {
    name: {
      type: String as PropType<string>,
      default: "default",
    },
    route: Object as PropType<RouteLocationNormalizedLoaded>,
  },
  setup(props, { attrs, slots }) {
    // 获取当前路由及匹配项
    const injectedRoute = inject(routerViewLocationKey)!;
    const routeToDisplay = computed<RouteLocationNormalizedLoaded>(
      () => props.route || injectedRoute.value
    );
    const injectedDepth = inject(viewDepthKey, 0);
    const matchedRouteRef = computed<RouteLocationMatched | undefined>(
      () => routeToDisplay.value.matched[depth.value]
    );
    // 返回值 为RouterView组件 render 函数
    return () => {
      // 当前路由
      const route = routeToDisplay.value;
      // 提取 命中路由记录中对应名字视图里的 对应组件
      const currentName = props.name;
      const matchedRoute = matchedRouteRef.value;
      const ViewComponent =
        matchedRoute && matchedRoute.components![currentName];
      // 创建 vnode
      const component = h(
        ViewComponent,
        assign({}, routeProps, attrs, {
          onVnodeUnmounted,
          ref: viewRef,
        })
      );
      return component;
    };
  },
});

辅助信息集锦

ts
export const START_LOCATION_NORMALIZED: RouteLocationNormalizedLoaded = {
  path: "/",
  name: undefined,
  params: {},
  query: {},
  hash: "",
  fullPath: "/",
  matched: [],
  meta: {},
  redirectedFrom: undefined,
};
Vue-Router源码分析 has loaded