vue-router v4.x核心原理

router-view视图组件通过监听响应式对象currentRoute实现视图的更新。

router/packages/router/src/router.ts

js 复制代码
export function createRouter(options: RouterOptions): Router {
    const currentRoute = shallowRef<RouteLocationNormalizedLoaded>(
        START_LOCATION_NORMALIZED
      )
    const router : Router = {
        install(app: App) {
          const router = this
          app.component('RouterLink', RouterLink)
          app.component('RouterView', RouterView)

          if (
            isBrowser &&
            // used for the initial navigation client side to avoid pushing
            // multiple times when the router is used in multiple apps
            !started &&
            currentRoute.value === START_LOCATION_NORMALIZED
          ) {
            // see above
            started = true
            push(routerHistory.location).catch(err => {
              if (__DEV__) warn('Unexpected error when starting the router:', err)
            })
          }
            ```
          app.provide(routerViewLocationKey, currentRoute)}
        }
    return router
}

router/packages/router/src/RouterView.ts

js 复制代码
// router-view组件
export const RouterViewImpl = /*#__PURE__*/ defineComponent({
  name: 'RouterView',
  inheritAttrs: false,
  props: {
    name: {
      type: String as PropType<string>,
      default: 'default',
    },
    route: Object as PropType<RouteLocationNormalizedLoaded>,
  },

  // Better compat for @vue/compat users
  // https://github.com/vuejs/router/issues/1315
   ...
    __DEV__ && warnDeprecatedUsage()

    const injectedRoute = inject(routerViewLocationKey)!
    const routeToDisplay = computed<RouteLocationNormalizedLoaded>(
      () => props.route || injectedRoute.value
    )
    const matchedRouteRef = computed<RouteLocationMatched | undefined>(
      () => routeToDisplay.value.matched[depth.value]
    )

    // watch at the same time the component instance, the route record we are
    // rendering, and the name
    watch(
      () => [viewRef.value, matchedRouteRef.value, props.name] as const,
      ([instance, to, name], [oldInstance, from, oldName]) => {
        // copy reused instances
        if (to) {
          // this will update the instance for new instances as well as reused
          // instances when navigating to a new route
          to.instances[name] = instance
          // the component instance is reused for a different route or name, so
          // we copy any saved update or leave guards. With async setup, the
          // mounting component will mount before the matchedRoute changes,
          // making instance === oldInstance, so we check if guards have been
          // added before. This works because we remove guards when
          // unmounting/deactivating components
          if (from && from !== to && instance && instance === oldInstance) {
            if (!to.leaveGuards.size) {
              to.leaveGuards = from.leaveGuards
            }
            if (!to.updateGuards.size) {
              to.updateGuards = from.updateGuards
            }
          }
        }

        // trigger beforeRouteEnter next callbacks
        if (
          instance &&
          to &&
          // if there is no instance but to and from are the same this might be
          // the first visit
          (!from || !isSameRouteRecord(to, from) || !oldInstance)
        ) {
          ;(to.enterCallbacks[name] || []).forEach(callback =>
            callback(instance)
          )
        }
      },
      { flush: 'post' }
    )

finalizeNavigation方法中会对响应式对象currentRoute重新赋值

js 复制代码
function finalizeNavigation(toLocation, from, isPush, replace2, data) {
    const error = checkCanceledNavigation(toLocation, from);
    if (error)
      return error;
    const isFirstNavigation = from === START_LOCATION_NORMALIZED;
    const state = !isBrowser ? {} : history.state;
    if (isPush) {
      if (replace2 || isFirstNavigation)
        routerHistory.replace(toLocation.fullPath, assign({
          scroll: isFirstNavigation && state && state.scroll
        }, data));
      else
        routerHistory.push(toLocation.fullPath, data);
    }
    currentRoute.value = toLocation;
    handleScroll(toLocation, from, isPush, isFirstNavigation);
    markAsReady();
  }

什么情况下会触发finalizeNavigation方法呢?

  • 点击自定义组件 router-link 创建的链接会触发finalizeNavigation方法,调用链如下:

点击自定义组件 router-link后,也会调用historypushState或者replaceState方法修改url。pushStatereplaceState不会触发popstate事件

js 复制代码
 function changeLocation(to, state, replace2) {
    const hashIndex = base.indexOf("#");
    const url = hashIndex > -1 ? (location2.host && document.querySelector("base") ? base : base.slice(hashIndex)) + to : createBaseLocation() + base + to;
    try {
      history2[replace2 ? "replaceState" : "pushState"](state, "", url);
      historyState.value = state;
    } catch (err) {
      if (true) {
        warn("Error with push/replace State", err);
      } else {
        console.error(err);
      }
      location2[replace2 ? "replace" : "assign"](url);
    }
  }
  • 手动修改url或者点击浏览器的回退前进按钮,也会触发finalizeNavigation方法,调用链如下:

此时会触发window上绑定的popstate事件的回掉函数,对比vue-router v4.x和 vue-router v3.x发现老版本中hash模式对应的hashchange事件已经没有了。

结果实践后发现改变url上的hash部分也会触发popstate事件。

router/packages/router/src/history/html5.ts

js 复制代码
  window.addEventListener('popstate', popStateHandler)
  
  const popStateHandler: PopStateListener = ({
    state,
  }: {
    state: StateEntry | null
  }) => {
    const to = createCurrentLocation(base, location)
    const from: HistoryLocation = currentLocation.value
    const fromState: StateEntry = historyState.value
    let delta = 0

    if (state) {
      currentLocation.value = to
      historyState.value = state

      // ignore the popstate and reset the pauseState
      if (pauseState && pauseState === from) {
        pauseState = null
        return
      }
      delta = fromState ? state.position - fromState.position : 0
    } else {
      replace(to)
    }
  }

vue-router v3.x的监听事件hashchange和popstate

js 复制代码
setupListeners () {
    if (this.listeners.length > 0) {
      return
    }

    const router = this.router
    const eventType = supportsPushState ? 'popstate' : 'hashchange'
    window.addEventListener(
      eventType,
      handleRoutingEvent
    )
    this.listeners.push(() => {
      window.removeEventListener(eventType, handleRoutingEvent)
    })
  }
相关推荐
野猪佩奇007几秒前
uni-app使用ucharts地图,自定义Tooltip鼠标悬浮显示内容并且根据@getIndex点击事件获取点击的地区下标和地区名
前端·javascript·vue.js·uni-app·echarts·ucharts
2401_8570262315 分钟前
拖动未来:WebKit 完美融合拖放API的交互艺术
前端·交互·webkit
星辰中的维纳斯1 小时前
vue新手入门教程(项目创建+组件导入+VueRouter)
前端·javascript·vue.js
嫣嫣细语1 小时前
css实现鼠标禁用(鼠标滑过显示红色禁止符号)
前端·css
Days20501 小时前
web前端主要包括哪些技术
前端
XF鸭2 小时前
HTML-CSS 入门介绍
服务器·前端·javascript
forwardMyLife2 小时前
element-plus 的form表单组件之el-radio(单选按钮组件)
前端·javascript·vue.js
fs哆哆3 小时前
ExcelVBA运用Excel的【条件格式】(二)
linux·运维·服务器·前端·excel
安冬的码畜日常3 小时前
【CSS in Depth 2精译】2.5 无单位的数值与行高
前端·css
ilisi_3 小时前
导航栏样式,盒子模型
前端·javascript·css