SoFunction
Updated on 2025-04-06

vue router source code overview case analysis

Source code actually has no immediate effect on actual work. It will not be used in actual projects like those highly targeted articles and can be used immediately in actual projects. The role of source code is a subtle process. After reading its concepts, design patterns, code structures, etc., it may not be immediately monetized (or very little monetization), but will be quietly used in the future work process. You can't even feel this process.

In addition, excellent source code cases, such as vue and react, have a large amount of content and cannot be finished in three, five, ten, or eight articles. Moreover, it is difficult to write clearly and is a waste of time. If you only analyze one of the points, such as vue's responsiveness, there are enough similar articles, so there is no need to repeat them again.

So I haven't written a source code analysis article before, I just read it myself. However, I have been free recently and read the source code of vue-router and found that this kind of plug-in-level thing is simple and clear than the framework level of vue, with less logic and not much code. However, the concepts and other things contained in it are very refined and worth writing. Of course, the article is just an overview, and I can't analyze the past one by one, so I still have to look at the details by myself.

The vue plug-in must be registered with . The code of , is located in the src/core/global-api/ file of the vue source code. The main functions of this method are:

  • Caches registered components to avoid registering the same plug-in multiple times
if ((plugin) > -1) {
 return this
}
  • Call the install method of the plug-in or run the plug-in directly to implement the install of the plug-in
if (typeof  === 'function') {
 (plugin, args)
} else if (typeof plugin === 'function') {
 (null, args)
}

Routing installation

The install method of vue-router is located in src/ of the vue-router source code. It mainly uses the mixing of beforeCreate and destroyed hook functions, and globally registers the router-view and router-link components.

// src/
({
 beforeCreate () {
  if (isDef(this.$)) {
   this._routerRoot = this
   this._router = this.$
   this._router.init(this)
   (this, '_route', this._router.)
  } else {
   this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
  }
  registerInstance(this, this)
 },
 destroyed () {
  registerInstance(this)
 }
})
...
// Global registration of `router-view` and `router-link` components('RouterView', View)
('RouterLink', Link)

Routing mode

vue-router supports three routing modes (mode): hash, history, abstract, where abstract is a routing mode used in a non-browser environment, such as weex

The route will judge the route mode specified in the external specified externally. For example, the current environment is a non-browser environment, no matter what mode is passed in, it will be forced to be abstract. If it is judged that the current environment does not support HTML5 History, it will eventually be downgraded to hash mode.

// src/
let mode =  || 'hash'
 = mode === 'history' && !supportsPushState &&  !== false
if () {
 mode = 'hash'
}
if (!inBrowser) {
 mode = 'abstract'
}

Finally, the corresponding initialization operation will be performed on the mode that meets the requirements.

// src/
switch (mode) {
 case 'history':
   = new HTML5History(this, )
  break
 case 'hash':
   = new HashHistory(this, , )
  break
 case 'abstract':
   = new AbstractHistory(this, )
  break
 default:
  if (.NODE_ENV !== 'production') {
   assert(false, `invalid mode: ${mode}`)
  }
}

Routing analysis

Recursively parse nested routes

// src/
function addRouteRecord (
 pathList: Array<string>,
 pathMap: Dictionary<RouteRecord>,
 nameMap: Dictionary<RouteRecord>,
 route: RouteConfig,
 parent?: RouteRecord,
 matchAs?: string
) {
 ...
 (child => {
  const childMatchAs = matchAs
   ? cleanPath(`${matchAs}/${}`)
   : undefined
  addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs)
 })
 ...
}

After parsing, the parsed route will be recorded in the form of a key-value pair. Therefore, if multiple route maps with the same path (path) are declared, only the first one will work, and the next one will be ignored.

// src/
if (!pathMap[]) {
 ()
 pathMap[] = record
}

For example, the following routing configuration, routing /bar will only match Bar1, and the Bar2 configuration will be ignored

const routes = [
 { path: '/foo', component: Foo },
 { path: '/bar', component: Bar1 },
 { path: '/bar', component: Bar2 },
];

Routing switch

When accessing a url, vue-router will match the path and create a route object, which can be accessed through this.$route

// src/util/
const route: Route = {
 name:  || (record && ),
 meta: (record && ) || {},
 path:  || '/',
 hash:  || '',
 query,
 params:  || {},
 fullPath: getFullPath(location, stringifyQuery),
 matched: record ? formatMatch(record) : []
}

transitionTo() in src/history/ source code file is the core method of routing switching

transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) {
 const route = (location, )
 (route, () => {
 ...
}

The route switching methods such as push and replace of the routing instance are based on this method to realize route switching, such as the push method in hash mode:

// src/history/
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
 const { current: fromRoute } = this
 // Used the transitionTo method (location, route =&gt; {
  pushHash()
  handleScroll(, route, fromRoute, false)
  onComplete &amp;&amp; onComplete(route)
 }, onAbort)
}

The transitionTo method is internally updated with a mode of asynchronous function queued execution, and executes asynchronous callbacks through the next function, and executes the corresponding hook function (i.e., navigation guard) in the asynchronous callback method beforeEach , beforeRouteUpdate , beforeRouteEnter , beforeRouteLeave

Save the corresponding routing parameters through the queue array:

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

RunQueue starts the execution of asynchronous function queueing in a recursive callback:

// src/history/
// Asynchronous callback functionrunQueue(queue, iterator, () =&gt; {
 const postEnterCbs = []
 const isValid = () =&gt;  === route
 // wait until async components are resolved before
 // extracting in-component enter guards
 const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid)
 const queue = ()
 // Recursive execution runQueue(queue, iterator, () =&gt; {
  if ( !== route) {
   return abort()
  }
   = null
  onComplete(route)
  if () {
   .$nextTick(() =&gt; {
    (cb =&gt; { cb() })
   })
  }
 })
})

The callback iteration of the navigation guard is performed through next, so if the navigation hook function is explicitly declared in the code, then next() must be called at the end, otherwise the callback will not be executed and the navigation will not be able to continue

// src/history/
const iterator = (hook: NavigationGuard, next) => {
 ...
 hook(route, current, (to: any) => {
  ...
  } else {
   // confirm transition and pass on the value
   next(to)
  }
 })
...
}

Routing synchronization

During route switching, vue-router will call push, go and other methods to synchronize the view and address url

Synchronization of address bar url with view

When routing switch is performed by clicking buttons on the page, vue-router will keep the view and url synchronized by changing, such as routing switch in hash mode:

// src/history/
function pushHash (path) {
 if (supportsPushState) {
  pushState(getUrl(path))
 } else {
   = path
 }
}

The above code first checks whether the current browser supports the History API of html5. If it is supported, call this API to modify the href. Otherwise, the principle of directly assigning history is the same as this, and it also uses the History API.

Synchronization of view and address bar url

When clicking the forward and back button of the browser, the view synchronization can also be achieved. This is because when the route is initialized, an event listener for the forward and backward browser is set up.

The following is event listening in hash mode:

// src/history/
setupListeners () {
 ...
 (supportsPushState ? 'popstate' : 'hashchange', () => {
  const current = 
  if (!ensureSlash()) {
   return
  }
  (getHash(), route => {
   if (supportsScroll) {
    handleScroll(, route, current, true)
   }
   if (!supportsPushState) {
    replaceHash()
   }
  })
 })
}

The history pattern is similar to this:

// src/history/
('popstate', e => {
 const 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()
 if ( === START && location === initLocation) {
  return
 }

 (location, route => {
  if (supportsScroll) {
   handleScroll(router, route, current, true)
  }
 })
})

Whether it is hash or history, the transitionTo method is called by listening to the event at the end, thereby achieving the unification of routes and views.

In addition, when the page is accessed for the first time and the route is initialized, if it is in hash mode, the url will be checked. If it is found that the accessed url does not have # characters, it will be automatically appended, such as the first time you access ithttp://localhost:8080This url, vue-router will be automatically replaced withhttp://localhost:8080/#/, convenient for subsequent routing management:

// src/history/
function ensureSlash (): boolean {
 const path = getHash()
 if ((0) === '/') {
  return true
 }
 replaceHash('/' + path)
 return false
}

scrollBehavior

When jumping from one route /a to another route /b, if the scroll bar is scrolled in the page of route /a, when the page jumps to /b, you will find that the scroll bar position of the browser is the same as /a (if /b can also scroll), or refresh the current page, the scroll bar position of the browser remains unchanged and will not return directly to the top. If the route switch is controlled by clicking the forward and back buttons of the browser, the scroll bar of the department browser (such as WeChat) will automatically return to the top when the route switch is switched, that is, the scrollTop=0 position. These are the default behaviors of the browser. If you want to customize the scroll bar position during page switching, you can use scrollBehavior, the options of vue-router

When the route is initialized, vue-router will listen on the route's switching events, and part of the listening logic is used to control the location of the browser's scrollbar:

// src/history/
setupListeners () {
 ...
 if (supportsScroll) {
  //Control event control of browser scrollbar  setupScroll()
 }
 ...
}

The set method is defined in src/util/ . This file is specifically used to control the scroll bar position. By listening to route switching events, scroll bar position control is performed:

// src/util/
('popstate', e => {
 saveScrollPosition()
 if ( && ) {
  setStateKey()
 }
})

ScrollBehavior can customize the scrollbar position for routing switching. There are related examples in the source code of vue-router github, and the source code position is vue-router/examples/scroll-behavior/

router-view & router-link

The two built-in components of the router-view and router-link are located in src/components

router-view

router-view is a functional component with stateless (no responsive data) and no instance (no this context). It obtains the corresponding component instance through route matching, and dynamically generates components through h function. If the current route does not match any components, a comment node will be rendered.

// vue-router/src/components/
...
const matched = [depth]
// render empty node if no matched route
if (!matched) {
 cache[name] = null
 return h()
}
const component = cache[name] = [name]
...
return h(component, data, children)

Each route switch will trigger router-view to render a new view, which is declared when vue-router initializes init:

// src/
({
 beforeCreate () {
  if (isDef(this.$)) {
   this._routerRoot = this
   this._router = this.$
   this._router.init(this)
   // Trigger router-view re-render   (this, '_route', this._router.)
   ...
})

Turn this._route into a responsive data through defineReactive. This defineReactive is a method defined in vue and is used to turn data into responsive. The source code is in vue/src/core/observer/, and its core is to modify the getter and setter of data through methods:

(obj, key, {
  enumerable: true,
  configurable: true,
  get: function reactiveGetter () {
   const value = getter ? (obj) : val
   if () {
    // Conduct dependency collection    ()
    if (childOb) {
     ()
     if ((value)) {
      dependArray(value)
     }
    }
   }
   return value
  },
  set: function reactiveSetter (newVal) {
   ...
   // Notify observers who subscribe to the current data to respond   ()
  }

When the route changes, the render function of router-view will be called. This function accesses the data of this._route, which is equivalent to calling the getter method of this._route, triggering dependency collection, establishing a Watcher, and executing the _update method, so that the page can be re-rendered

// vue-router/src/components/
render (_, { props, children, parent, data }) {
 // used by devtools to display a router-view badge
  = true

 // directly use parent context's createElement() function
 // so that components rendered by router-view can resolve named slots
 const h = parent.$createElement
 const name = 
 // Trigger dependency collection and create a render watcher const route = parent.$route
 ...
}

The distribution update of the render watcher, that is, the call to the setter, is located in src/:

(route =&gt; {
 ((app) =&gt; {
  // Trigger setter  app._route = route
 })
})

router-link

When executing the render function, router-link will add class to the rendered active elements based on the current routing state, so you can use this to set styles for active routing elements, etc.:

// src/components/
render (h: Function) {
 ...
 const globalActiveClass = 
 const globalExactActiveClass = 
 // Support global empty active class
 const activeClassFallback = globalActiveClass == null
  ? 'router-link-active'
  : globalActiveClass
 const exactActiveClassFallback = globalExactActiveClass == null
  ? 'router-link-exact-active'
  : globalExactActiveClass
  ...
}

The default rendering element of router-link is the <a> tag, which will add the href attribute value to this <a> and some events that can trigger routing switching. The default is the click event:

// src/components/
 = on
 = { href }

In addition, you can customize the element tags rendered by router-link by passing in tag props:

<router-link to="/foo" tag="div">Go to foo</router-link>

If the tag value is not a , it will recursively traverse the child elements of router-link until a tag is found, and the event and route are assigned to this <a>. If the a tag is not found, the event and route are placed on the element rendered by router-link:

if ( === 'a') {
   = on
   = { href }
 } else {
  // find the first &lt;a&gt; child and apply listener and href
  // findAnchor is a method to recursively traverse child elements  const a = findAnchor(this.$)
  ...
 }
}

When these route switching events are triggered, the corresponding method is called to switch route refresh view:

// src/components/
const handler = e =&gt; {
 if (guardEvent(e)) {
  if () {
   // replace route   (location)
  } else {
   // push route   (location)
  }
 }
}

Summarize

You can see that the source code of vue-router is very simple and is more suitable for novices to read and analyze.

My understanding of the source code is that there is no need to make time to read it. As long as you read the documents carefully and can use the API correctly and skillfully to achieve various needs, it is enough. The appearance of the wheel is originally for actual development rather than to trouble developers. Note that I am not saying that you should not read it. You still have to read it if you have time. Even if you can't figure out the rules, you will always get rewards after reading it. For example, when I was reading the vue source code, I often saw assignment writing similar to this:

// vue/src/core/vdom/
( || ( = {})).slot = 

If it was before, I would usually write this logic like this:

if () {
  = 
} else {
  = {
  slot: 
 }
}

It’s not that the first way of writing is difficult or not clear. I’m just used to the second way of writing. I write it naturally without thinking during the process of writing code. The habit becomes natural. But when I see the first way of writing, I will slap my head and think that it’s okay to write it like this. I used to typing the keyboard so many times in vain, so I have to look at other people’s excellent source codes to avoid being addicted to my own world and work behind closed doors, so that I can check for omissions and fill in the gaps. This is also the reason why I think the code review is more important. It’s difficult for others to find problems. Others may see it at a glance. This is what the authorities are confused and the bystanders are clear.

The above is an overview of the vue router source code introduced by the editor. I hope it will be helpful to you. If you have any questions, please leave me a message and the editor will reply to you in time. Thank you very much for your support for my website!