Vue – the router source code parsing | 6 k word – 【 the 】

  • Hello everyone, I am Guanghui 😎
  • This isvue-routerSource code analysis of the next chapter, that is the end
  • This article mainly introduces the following points
    • This paper introduces thevue-routerHow is scrolling handled
    • The view, the linkHow are components implemented?
    • How do route changes trigger rerendering and so on
  • Another thing to say
    • The first time to do source code analysis, there must be a lot of mistakes or understanding is not in place, welcome to correct 🤞
  • The project address
    • https://github.com/BryanAdamss/vue-router-for-analysis
    • Give me one if you think it’ll helpstar
  • Uml diagram source file
    • https://github.com/BryanAdamss/vue-router-for-analysis/blob/dev/vue-router.EAP
  • Associated article link
    • Vue – the router source code parsing words | | 1.3 w multiple warning – [on]
    • Vue – the router source code parsing words | | 1.5 w multiple warning – 【 in 】
    • Vue – the router source code parsing | 6 k word – 【 the 】

The rolling process

  • We know thatvue-routerIt can handle some scrolling behavior, such as recording the page scrolling position and then rolling to the top or keeping the original position when switching routes;
    • Router.vuejs.org/zh/guide/ad…
  • It basically receives onescrollBehaviorParameters,scrollBehaviorThere are the following ways to play

const router = new VueRouter({
      routes: [...]. , scrollBehavior (to,from, savedPosition) {
        // return is expected to scroll to the position}})// Scroll to the specified coordinates
scrollBehavior (to, from, savedPosition) {
  return { x: 0.y: 0}}// Scroll to the specified page anchor
scrollBehavior (to, from, savedPosition) {
  if (to.hash) {
    return {
      selector: to.hash
    }
  }
}

// v2.8.0+ asynchronous scrolling
scrollBehavior (to, from, savedPosition) {
      return new Promise((resolve, reject) = > {
        setTimeout(() = > {
          resolve({ x: 0.y: 0})},500)})}Copy the code
  • Supports scrolling to a specified location, a page anchor location, and asynchronous scrolling
  • So how does it do that? What’s the logic?
  • We all know thatHTML5HistoryAnd at initializationHashHistoryinsetupListenerIs called whensetupScrollFunction to initialize the rolling-related logic
  • And in thepopstateorhashchangeCalled when the event triggers a route jumphandleScrollHandling scrolling behavior
// src/history/hash.js

 setupListeners () {
    const router = this.router
    const expectScroll = router.options.scrollBehavior
    const supportsScroll = supportsPushState && expectScroll

    // If scroll is supported, initialize the scroll logic
    if (supportsScroll) {
      setupScroll()
    }
    // Add event listener
    window.addEventListener(
      supportsPushState ? 'popstate' : 'hashchange'.// PopState is preferred
      () = > {
        const current = this.current
        if(! ensureSlash()) {return
        }
        this.transitionTo(getHash(), route= > {
          if (supportsScroll) {
            handleScroll(this.router, /* to*/route, /* from*/current, true)}// Do not support pushState, directly replace records
          if(! supportsPushState) { replaceHash(route.fullPath) } }) } ) }Copy the code
  • Let’s first look at the initialization of the scroll

setupScroll

  • The code is located in thesrc/util/scroll.js
// Initialize the rolling-related logic
export function setupScroll() {
  // Fix for #1585 for Firefox
  // Fix for #2195 Add optional third attribute to workaround a bug in safari https://bugs.webkit.org/show_bug.cgi?id=182678
  // Fix for #2774 Support for apps loaded from Windows file shares not mapped to network drives: replaced location.origin with
  // window.location.protocol + '//' + window.location.host
  // location.host contains the port and location.hostname doesn't
  const protocolAndPath = window.location.protocol + '/ /' + window.location.host
  const absolutePath = window.location.href.replace(protocolAndPath, ' ') // preserve existing history state as it could be overriden by the user

  const stateCopy = extend({}, window.history.state)
  stateCopy.key = getStateKey() ReplaceState (stateObj, title[, url]);

  window.history.replaceState(stateCopy, ' ', absolutePath) // Listen for popState (triggered only by the browser's forward/back buttons), save the scroll position, and update the stateKey

  window.addEventListener('popstate'.(e) = > {
    saveScrollPosition()
    if (e.state && e.state.key) {
      setStateKey(e.state.key)
    }
  })
}
Copy the code
  • You can see the utilizationHistory APITo complete the saving of the location
    • Developer.mozilla.org/zh-CN/docs/…
  • inpopstateRecord the scrolling position and update the statusobjthekey
  • thiskeyIs used instateIs used to identify each route
  • May have a lookkeyThe access
// src/util/state-key.js

// use User Timing api (if present) for more accurate key precision
const Time =
  inBrowser && window.performance && window.performance.now
    ? window.performance
    : Date

// Generate a unique key that identifies each route in state
export function genStateKey() :string {
  return Time.now().toFixed(3)}let _key: string = genStateKey()
export function getStateKey() {
  return _key
}

export function setStateKey(key: string) {
  return (_key = key)
}
Copy the code
  • You can see that one is declared_key, which is a three-digit timestamp. Updates and reads are operated on this one_key
  • setupScroll, first copy the currentstateAnd generates a unique for itkey
  • throughreplaceStateWill be addedkeythestateSaved to the current routeabsolutePathon
  • Then listen topopstateEvent, which can only be triggered by the browser’s forward/back buttons
  • When triggered, the current location is saved and updated_key
  • This can be triggered when the route changespopstateSave the current location and set unique_key
  • How does it access location information
// src/util/scroll.js

const positionStore = Object.create(null) // Save the page scroll position
export function saveScrollPosition() {
  const key = getStateKey()
  if (key) {
    positionStore[key] = {
      x: window.pageXOffset,
      y: window.pageYOffset,
    }
  }
}
Copy the code
  • The use ofpositionStoreObject matches unique_keyTo access the location
  • inhandleScrollWhen you can pass_keyRetrieve the previously saved position

handleScroll

  • The code that handles scrolling is locatedsrc/util/scroll.js

export function handleScroll (
  router: Router,
  to: Route,
  from: Route,
  isPop: boolean// PopState is only triggered by the browser's forward/back buttons, and only popState will save the scroll position
{

  if(! router.app) {return
  }
  const behavior = router.options.scrollBehavior
  if(! behavior) {return
  }
  if(process.env.NODE_ENV ! = ='production') {
    assert(typeof behavior === 'function'.`scrollBehavior must be a function`)}// wait until re-render finishes before scrolling
  // Re-render the end, then process the scroll
  router.app.$nextTick(() = > {
    const position = getScrollPosition() // Get the scroll position saved previously

    // https://router.vuejs.org/zh/guide/advanced/scroll-behavior.html#%E6%BB%9A%E5%8A%A8%E8%A1%8C%E4%B8%BA
    const shouldScroll = behavior.call(
      router,
      to,
      from,
      isPop ? position : null // The third parameter savedPosition is available if and only if popState navigation (triggered by the browser's forward/back buttons). , so save Position is only available when it is popState
    )
    // When a falsy value is returned, no scrolling is required
    if(! shouldScroll) {return
    }

    // v.2.8.0 Supports asynchronous scrolling
    // https://router.vuejs.org/zh/guide/advanced/scroll-behavior.html#%E5%BC%82%E6%AD%A5%E6%BB%9A%E5%8A%A8
    if (typeof shouldScroll.then === 'function') {
      shouldScroll
        .then(shouldScroll= > {
          scrollToPosition((shouldScroll: any), position)
        })
        .catch(err= > {
          if(process.env.NODE_ENV ! = ='production') {
            assert(false, err.toString())
          }
        })
    } else {
      scrollToPosition(shouldScroll, position)
    }
  })
}
Copy the code
  • in$nextTickIn the callgetScrollPositionGets the previously saved location
  • Call the one that we passed inscrollBehaviorLook at its return value to determine if scrolling is required
  • It also determines whether a wave is rolling asynchronously
  • If so, wait for itresolvedCall againscrollToPosition
  • Otherwise callscrollToPosition
  • getScrollPosition,scrollToPositionThe following code
// src/util/scroll.js

// Get the saved scroll position
function getScrollPosition(): ?Object {
  const key = getStateKey() // take a unique key
  if (key) {
    return positionStore[key] / / position}}// Scroll to a specified position. Supports scrolling to a specific element
// https://router.vuejs.org/zh/guide/advanced/scroll-behavior.html#%E6%BB%9A%E5%8A%A8%E8%A1%8C%E4%B8%BA
function scrollToPosition(shouldScroll, position) {
  const isObject = typeof shouldScroll === 'object' // Scroll to a specific DOM

  if (isObject && typeof shouldScroll.selector === 'string') {
    // getElementById would still fail if the selector contains a more complicated query like #main[data-attr]
    // but at the same time, it doesn't make much sense to select an element with an id and an extra selector
    const el = hashStartsWithNumberRE.test(shouldScroll.selector) // $flow-disable-line
      ? document.getElementById(shouldScroll.selector.slice(1)) // $flow-disable-line
      : document.querySelector(shouldScroll.selector)
    if (el) {
      let offset =
        shouldScroll.offset && typeof shouldScroll.offset === 'object'
          ? shouldScroll.offset
          : {}
      offset = normalizeOffset(offset)
      position = getElementPosition(el, offset)
    } else if (isValidPosition(shouldScroll)) {
      position = normalizePosition(shouldScroll)
    }
  } else if (isObject && isValidPosition(shouldScroll)) {
    // Scroll directly to the specified position
    position = normalizePosition(shouldScroll)
  }
  if (position) {
    window.scrollTo(position.x, position.y)
  }
}
Copy the code
  • To get the scroll position, use_keyfrompositionStoreTo read the saved location information
  • scrollToPositionThe logic is clear, and it handles scrolling to the specifieddomAnd scroll directly to a specific location

summary

  • vue-routerHandling scrolling is mainly utilizedHistory APIFeature implementations that can save state
  • Save the scroll position before the route enters, and try to retrieve the previous position the next time the route changes, in$nextTickIn the real deal with scrolling
  • It supports scenarios such as scrolling to a specified location, specified DOM, and asynchronous scrolling

The view components

  • vue-routerThe built-inrouter-view,router-linkTwo components
  • The former is responsible for rendering the corresponding routing component after matching the routing record
  • The latter allows users to navigate (click) in routing applications
  • Let’s look at it firstrouter-viewcomponent

router-view

  • router-viewThe primary responsibility of the routing component is to render it
  • Defined in thesrc/components/view.js
// src/components/view.js

export default {
  name: 'RouterView'.functional: true.// Functional components without this; https://cn.vuejs.org/v2/guide/render-function.html# functional components
  props: {
    name: {
      type: String.default: 'default',}},// _ createElement, but router-View does not use its own h, but uses the parent h
  render(/* h*/ _, /* context*/ { props, children, parent, data }) {
    // used by devtools to display a router-view badge
    data.routerView = true // Directly use parent context's createElement() function // so that components rasterby router-view can resolve named slots
    const h = parent.$createElement // Use the parent node's rendering function
    const name = props.name // Name the view
    const route = parent.$route // It depends on the parent node's $route. In install.js, all components access $route as _routerroot. _route, that is, _route on the root instance of Vue. When the route is confirmed and updateRoute is called, _RouterRoot. _route is updated, which causes the router-View component to re-render // cache
    const cache = parent._routerViewCache || (parent._routerViewCache = {}) // determine current view depth, also check to see if the tree // has been toggled inactive but kept-alive.

    let depth = 0 // The current nesting depth of router-view
    let inactive = false // Is wrapped by keep-alive and inactive // Look up, calculate depth, inactive // Loop ends when parent points to Vue root instance
    while(parent && parent._routerRoot ! == parent) {const vnodeData = parent.$vnode ? parent.$vnode.data : {}
      if (vnodeData.routerView) {
        depth++
      } // The keep-alive component adds a keepAlive=true flag // https://github.com/vuejs/vue/blob/52719ccab8fccffbdf497b96d3731dc86f04c1ce/src/core/components/keep-alive.js#L120

      if (vnodeData.keepAlive && parent._directInactive && parent._inactive) {
        inactive = true
      }
      parent = parent.$parent
    }

    data.routerViewDepth = depth // Render previous view if the tree is inactive and kept-alive // Render the previously saved view if the current component tree is wrapped by keep-alive and inactive

    if (inactive) {
      const cachedData = cache[name]
      const cachedComponent = cachedData && cachedData.component // Find the cached component
      if (cachedComponent) {
        / / # 2301
        // pass props
        // Pass the props for the cache
        if (cachedData.configProps) {
          fillPropsinData(
            cachedComponent,
            data,
            cachedData.route,
            cachedData.configProps
          )
        }
        return h(cachedComponent, data, children)
      } else {
        // No cached component found
        // render previous empty view
        return h()
      }
    } // As the formatMatch method is used by unshift to add the parent route record, route.matched[depth] can obtain the matched route record

    const matched = route.matched[depth]
    const component = matched && matched.components[name] // Render empty node if no matched route or no config component //
    if(! matched || ! component) { cache[name] =null
      return h()
    } // cache component // Cache component

    cache[name] = { component } // Attach instance registration hook // This will be called in the instance's injected lifecycle hooks // This method only defines // vm on router-view. // This method only defines // VM on router-view. {// default:VueComp, // hd:VueComp2, // bd:VueComp3 //}

    data.registerRouteInstance = (vm, val) = > {
      // val could be undefined for unregistration
      const current = matched.instances[name]
      if( (val && current ! == vm) ||/ / binding(! val && current === vm) ) {// If val does not exist, it can be considered unbound
        matched.instances[name] = val
      }
    } // also register instance in prepatch hook // in case the same component instance is reused across different routes // If the same component is multiplexed between different routes, you also need to bind routing components to the router-view; (data.hook || (data.hook = {})).prepatch =(_, vnode) = > {
      matched.instances[name] = vnode.componentInstance
    } // Register instance in init hook // In case keep-alive component be actived when routes changed // Keep-alive component is activated The routing component needs to be registered with the router-view

    data.hook.init = (vnode) = > {
      if( vnode.data.keepAlive && vnode.componentInstance && vnode.componentInstance ! == matched.instances[name] ) { matched.instances[name] = vnode.componentInstance } }// route record sets the route transmission parameter. Dynamic route parameter transmission; https://router.vuejs.org/zh/guide/essentials/passing-props.

    const configProps = matched.props && matched.props[name] // Save route and configProps in cachce // If route and configProps are configured, the props will be cached and filled

    if (configProps) {
      extend(cache[name], {
        route,
        configProps,
      })
      fillPropsinData(component, data, route, configProps)
    }
    return h(component, data, children)
  },
}
Copy the code
  • It is defined as a functional component, which means that it has no state or instance (this context) and only receivesnameLet’s make a named view
  • Let’s focus on thatrendermethods
  • Because it is a functional component, many operations are done with the help of the parent node
    • In order to support parsing named slots, it does not use its owncreateElementMethod that uses the parent node insteadcreateElementmethods
    • Cannot pass because there is no this contextthis.$routeTo get the current route object, simply use the parent node’s$route
  • You can see that a marker has been addedrouterViewUsed mainly invue-devtoolsIdentified inviewComponent and when looking for depth
  • A cache object is then declared_routerViewCacheAnd assigned to itcacheVariable used in thekeep-aliveQuickly fetch cached routing components on activation
  • Start looking up from the current nodeVue root instanceAnd calculate it during the searchviewThe depth of the component and whether it iskepp-aliveWrapped and ininativestate
  • depthMainly used to get the currentviewIndicates the route record
    • As I said,vue-routerIs to support nested routines by, correspondingviewIt can also be nested
    • And when matching routing records, there is the following logic,If a route record matches, the parent route record must also match, it will keep looking up, find a parent record, passunshiftintoroute.matchedArray, so the parent record must come first, the child record must come second, and the current exact match must come last
      • seeSRC/util/route. Js formatMatch method
    • depthThe calculation in the encounterThe parent viewComponent, increment by 1, increment by searching up continuouslydepthUntil I findVue root instanceDidn’t stop
    • When to stoproute.matched[depth]The value is the currentviewIndicates the route record
    • With the routing record, we can pull the corresponding routing component instance from above and render it
      • How are routing records and routing component instances bound
  • We see firstThe inactiveHow does state render routing component instances
    • throughroute.matched[depth]Take out the currentviewMatched route records
    • Then fetch the corresponding routing component instance
    • If either the routing record or the routing component instance does not exist, the null node is rendered and resetcache[name]value
    • If both can be found, the component instance is cached first
      • If dynamic routing parameters are configured, the routing parameters are cached on the routing component instance and calledfillPropsinDatafillprops
    • callhRender the corresponding routing component instance
  • When a component is ininactiveState, we can go fromcacheFetch the previously cached routing component instances and routing parameters from the
  • The main process is above, but there is one important point left out
    • How are routing records and routing component instances bound?
    • I’m sure you’ve noticeddata.registerRouteInstanceMethod, yes, which is used to bind routing component instances to routing records

registerInstance

  • Let’s take a look at the downsizing first
  • Mainly in thesrc/install.jsThe global mix
export function install(Vue){...// Register global mixin
  Vue.mixin({
    beforeCreate () {
        ... 

      // Associate the routing component with the router-view component
      registerInstance(this.this)
    },
    destroyed () {
      // Delete the association between router-view and routing components when destroyed hook is triggered
      registerInstance(this)}}}Copy the code
  • You can see it’s mixed in globallybeforeCreate,destroyedHooks are called
  • The former passed in two VM instances, while the latter passed in only one
  • Let’s look at the implementation. The code is also locatedsrc/install.jsIn the
// Associate the routing component with the routing record and router-view
const registerInstance = (vm, callVal) = > {
  let i = vm.$options._parentVnode / / call the vm $options. _parentVnode. Data. RegisterRouteInstance method / / this method only exists in the router - the view component, the router - the view component definition in (.. /components/ view.js@71 line) // Therefore, if the parent node of the VM is router-view, the current VM is associated with the router-view, that is, the current VM is used as the routing component of the router-view
  if (isDef(i) && isDef((i = i.data)) && isDef((i = i.registerRouteInstance))) {
    i(vm, callVal)
  }
}
Copy the code
  • You can see it receives oneVm instancesandcallValAs the reference
  • Then take thevmAs the initial value of I
  • And then step by stepI assignedAnd judgeiWhether the definition
  • In the end,iThe value ofvm.$options._parentVnode.data.registerRouteInstance
  • We then pass in two incoming parametersiIn the call
  • Note that I is the method on the vm parent, not the method on the VM
  • Let’s do a global searchregisterRouteInstanceThe keyword is found to be defined only inview.jsStudent: Middle, which is equal torouter-viewIn the component
    • Combine that with the previous one, I isregisterRouteInstanceisThe vm parent nodeOn, and onlyrouter-viewThe component definesregisterRouteInstance
    • So, only whenvmisrouter-viewIs the child node ofregisterRouteInstanceMethod is called
    • i(vm, callVal)Can be expressed asvm._parentVnode.registerRouteInstance(vm,vm)
  • Look at theregisterRouteInstanceThe implementation of the
// src/components/view.js.// Bind the route component to the route record and call it in the beforeCreate, deStoryed hook of all components. See the registerInstance method in install.js
    // This method is defined only on router-view
    // vm and val are routing component instances
    / / the following
    // matched.instances:{
    // default:VueComp,
    // hd:VueComp2,
    // bd:VueComp3
    // }
    data.registerRouteInstance = (vm, val) = > {
      // val could be undefined for unregistration
      const current = matched.instances[name]
      if( (val && current ! == vm) ||/ / binding(! val && current === vm)// If val does not exist, it can be considered unbound
      ) {
        matched.instances[name] = val
      }
    }
Copy the code
  • matchedRecords of the routes currently matched are saved.nameIs the named view name
  • ifvalExists, and the current route component is different from the incoming one, reassign
  • ifvalDoes not exist, and the current route component is the same as the one passed in, also reassigns, but val isundefinedIs equivalent to untying
  • As you can see, the number of arguments is different, and a function implements both binding and unbinding
  • This method completes the binding and unbinding of the routing record and the routing component instance
  • So that we can be inviewcomponentrenderWhen, throughroute.matched[depth].components[name]Take the routing component and render it
  • There are also scenarios that need to be bound
    • If the same component is multiplexed between different routes, you need to bind the routing component to the routing record
    • keep-aliveWhen the component is activated, the routing component needs to be bound to the routing record

summary

  • router-viewIs a functional component that sometimes needs to leverage the parent’s capabilities, such as parsing named slots using the parent’s rendering function
  • throughrouterViewTo identify theviewComponents, convenientvue-devtoolsTo identify theviewComponents and determinationsviewThe depth of the component
  • Determine the current by looking upviewThe depth of thedepthThrough thedepthThe corresponding route record is obtained
  • Take it out again throughregisterInstanceBound routing component instance
  • If dynamic route parameters exist, fill them firstpropsAnd then render it
  • ifviewbekeep-aliveWrapped and ininactiveState, the routing component instance is fetched from the cache and rendered

How do I trigger rerender

  • In the navigation parsing section, we mentioned that once the navigation parsing is successful
  • Will be calledupdateRouteMethod, re – global_routerRoot._routenamely$routeThe assignment
// src/history/base.js

AfterEach hook is triggered after updating the route
  updateRoute (route: Route) {
    const prev = this.current
    this.current = route/ / update the current

    this.cb && this.cb(route) // Call the updateRoute callback, which reassigns _Routerroot. _route, triggering a re-rendering of the router-View. }Copy the code
  • inviewComponent, will be used$parent.$routeThe global_routerRoot._route
   // src/components/view.js. render (/* h*/_, /* context*/{ props, children, parent, data }) {
        ...

        const route = parent.$route // It depends on the parent node's $route. In install.js, all components access $route as _routerroot. _route, that is, _route on the root instance of Vue. When the route is confirmed and updateRoute is called, _RouterRoot. _route is updated, causing the router-View component to re-render. }Copy the code
  • And in theinstall.jsThe global mix, will_routeDefined as reactive, dependent_routeIn the place of_routeWhen changes are made, they are rerendered
// src/install.js

  // Register global mixin
  Vue.mixin({
    beforeCreate () {
         ...
        // Define the _route attribute responsively to ensure that the component is rerendered when the _route changes
        Vue.util.defineReactive(this.'_route'.this._router.history.current)
      }
  })
Copy the code
  • This completes the rendering loop,viewRely on$route, navigation resolution updated successfully$routeTo triggerviewApply colours to a drawing
  • Finished watchingviewComponent, let’s look at another componentrouter-link

Link component

  • router-linkComponents are defined insrc/components/link.jsIn the
  • It is mainly used to support users in the routing function of the application (click) navigation

router-link

/* @flow */
import { createRoute, isSameRoute, isIncludedRoute } from '.. /util/route'
import { extend } from '.. /util/misc'
import { normalizeLocation } from '.. /util/location'
import { warn } from '.. /util/warn'

// work around weird flow bug
const toTypes: Array<Function> = [String.Object]
const eventTypes: Array<Function> = [String.Array]
const noop = () = > {}

export default {
  name: 'RouterLink'.props: {
    to: {
      type: toTypes, // string | Location
      required: true,},tag: {
      type: String.default: 'a'.// The default a tag
    },
    exact: Boolean.// Is an exact match
    append: Boolean.// Whether to append
    replace: Boolean.// If true, call router.replace otherwise call router.push
    activeClass: String.// The name of the active class
    exactActiveClass: String.// The exact matching class name
    ariaCurrentValue: {
      // Make it accessible
      type: String.default: 'page',},event: {
      type: eventTypes, // The event that triggers navigation
      default: 'click',}},render(h: Function) {
    const router = this.$router
    const current = this.$route
    const { location, route, href } = router.resolve(
      this.to,
      current,
      this.append
    ) // Parse the destination location
    const classes = {}
    const globalActiveClass = router.options.linkActiveClass
    const globalExactActiveClass = router.options.linkExactActiveClass // Support global empty active class

    const activeClassFallback =
      globalActiveClass == null ? 'router-link-active' : globalActiveClass
    const exactActiveClassFallback =
      globalExactActiveClass == null
        ? 'router-link-exact-active'
        : globalExactActiveClass
    const activeClass =
      this.activeClass == null ? activeClassFallback : this.activeClass
    const exactActiveClass =
      this.exactActiveClass == null
        ? exactActiveClassFallback
        : this.exactActiveClass // The target route is used to compare whether it is the same as the current route

    const compareTarget = route.redirectedFrom
      ? createRoute(null, normalizeLocation(route.redirectedFrom), null, router)
      : route
    classes[exactActiveClass] = isSameRoute(current, compareTarget)
    classes[activeClass] = this.exact
      ? classes[exactActiveClass]
      : isIncludedRoute(current, compareTarget) // In the case of imprecise matching, determine whether the target route path contains the current route path
    const ariaCurrentValue = classes[exactActiveClass]
      ? this.ariaCurrentValue
      : null // Event processing

    const handler = (e) = > {
      if (guardEvent(e)) {
        if (this.replace) {
          router.replace(location, noop)
        } else {
          router.push(location, noop)
        }
      }
    }

    const on = { click: guardEvent }
    if (Array.isArray(this.event)) {
      this.event.forEach((e) = > {
        on[e] = handler
      })
    } else {
      on[this.event] = handler
    }
    const data: any = { class: classes } // Read the scope slot

    const scopedSlot =
      !this.$scopedSlots.$hasNormal &&
      this.$scopedSlots.default &&
      this.$scopedSlots.default({
        href,
        route,
        navigate: handler,
        isActive: classes[activeClass],
        isExactActive: classes[exactActiveClass],
      })

    if (scopedSlot) {
      // The scope slot has only one child element
      if (scopedSlot.length === 1) {
        return scopedSlot[0]}else if (scopedSlot.length > 1| |! scopedSlot.length) {// The scope slot provides multiple descendants or does not provide a prompt
        if(process.env.NODE_ENV ! = ='production') {
          warn(
            false.`RouterLink with to="The ${this.to}" is trying to use a scoped slot but it didn't provide exactly one child. Wrapping the content with a span element.`)}// When there are multiple descendants, wrap a span around the outer layer
        return scopedSlot.length === 0 ? h() : h('span', {}, scopedSlot)
      }
    } / / tag to a

    if (this.tag === 'a') {
      data.on = on
      data.attrs = { href, 'aria-current': ariaCurrentValue }
    } else {
      // if the tag is not a, find the first a-bound event of the descendant
      // find the first <a> child and apply listener and href

      const a = findAnchor(this.$slots.default)
      if (a) {
        // in case the <a> is a static node
        a.isStatic = false
        const aData = (a.data = extend({}, a.data))
        aData.on = aData.on || {} // Transform existing events in both objects into Arrays so we can push later
        for (const event in aData.on) {
          const handler = aData.on[event]
          if (event in on) {
            aData.on[event] = Array.isArray(handler) ? handler : [handler]
          }
        } // Process new listeners for router-link //
        for (const event in on) {
          if (event in aData.on) {
            // on[event] is always a function
            aData.on[event].push(on[event])
          } else {
            aData.on[event] = handler
          }
        }
        const aAttrs = (a.data.attrs = extend({}, a.data.attrs))
        aAttrs.href = href
        aAttrs['aria-current'] = ariaCurrentValue
      } else {
        // doesn't have <a> child, apply listener to self
        // If no event is found, the current element is bound to an event
        data.on = on
      }
    }

    return h(this.tag, data, this.$slots.default)
  },
}

// In special scenarios, click not to redirect the response
function guardEvent(e) {
  // don't redirect with control keys
  if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return // don't redirect when preventDefault called
  if (e.defaultPrevented) return // don't redirect on right click
  if(e.button ! = =undefined&& e.button ! = =0) return // don't redirect if `target="_blank"`
  if (e.currentTarget && e.currentTarget.getAttribute) {
    const target = e.currentTarget.getAttribute('target')
    if (/\b_blank\b/i.test(target)) return
  } // this may be a Weex event which doesn't have this method
  if (e.preventDefault) {
    e.preventDefault()
  }
  return true
}

// Recursively find the descendant a tag
function findAnchor(children) {
  if (children) {
    let child
    for (let i = 0; i < children.length; i++) {
      child = children[i]
      if (child.tag === 'a') {
        return child
      }
      if (child.children && (child = findAnchor(child.children))) {
        return child
      }
    }
  }
}
Copy the code
  • The implementation is a generic component that jumps to when clickedtoCorresponding routing function
  • Because support click need to identify the style class, accurate matchingexactScene, so throughsameRoute,isIncludedRouteTo achieve style class identification and accurate matching identification
  • When clicking, part of the special scene is shielded, such as clicking at the same timectrl,alt,shiftEtc.control keysWhen, do not jump
  • After we look at the components, let’s look at them againrouterAnd provide us with instance methods

Instance properties, methods

  • routerA lot of properties and methods are exposed
  • These properties and methods were also used in the previous source code section

Instance attributes

  • router.app
    • The Vue root instance of the router is configured
  • router.mode
    • Mode used for routing
  • router.currentRoute
    • The current route object is equal tothis.$route

Instance methods

  • Use register global navigator
    • router.beforeEach
    • router.beforeResolve
    • router.afterEach
  • Programmatic navigation is relevant
    • router.push
    • router.replace
    • router.go
    • router.back
    • router.forward
  • Server side rendering is relevant
    • router.getMatchedComponents
      • Returns an array of components matching the target location or the current route (array definition/constructor class, not instance)
    • router.onReady
      • This method queues up a callback to be called when the route completes its initial navigation, which means it can resolve all asynchronous incoming hooks and route initialization associated with asynchronous components
    • router.onError
      • Registers a callback to be invoked when errors occur during routing navigation
  • Dynamic routing
    • router.addRoutes
      • Dynamically add routing rules
  • parsing
    • router.resolve– Pass in an object, attempt to parse and return a destination location

conclusion

  • So, we’re donevue-router@2All source code analysis 🎉
  • If you think you can, remember to help me like 👍

reference

  • Github.com/dwqs/blog/i…
  • Github.com/dwqs/blog/i…
  • Github.com/dwqs/blog/i…
  • Github.com/vuejs/vue-r…
  • Juejin. Cn/post / 684490…
  • Juejin. Cn/post / 684490…
  • Juejin. Cn/post / 684490…
  • Juejin. Cn/post / 684490…
  • www.jianshu.com/p/29e8214d0…
  • Ustbhuangyi. Making. IO/vue – analysi…
  • Blog.liuyunzhuge.com/2020/04/08/…

PS

  • The rest will be introduced later. If you think it’s ok, you can give it a thumbs up at ✨
  • personalgithub, also summed up a few things, welcome star
  • Drawing -board based on Canvas
  • Front-end introduction Demo, best practices collection FE-awesome -demos
  • One that automatically generates aliasesvue-cli-pluginwww.npmjs.com/package/vue…

NPM package

  • vue-cli-plugin-auto-alias
  • @bryanadamss/drawing-board
  • @bryanadamss/num2chn
  • ant-color-converter