preface

Before formally entering vuE-Router, we first jump out of the framework thinking, carefully consider the implementation principle of front-end routing, observe the following case.

<body>
    <a onclick="jump('/a')">A</a>
    <a onclick="jump('/b')">B</a>
    <a onclick="jump('/c')">C</a>
    <div id="app">
         
    </div>
 </body>   
Copy the code

Create three NEW A tags in the HTML file and place an empty div underneath.

Before taking a look at the JAVASCRIPT code, let’s take a quick look at the HTML5 History API.

  • history.pushStateA routing record can be added to the routing stack to disguise a jump to the page. Because of thisAPIThe call just changedurl“Does not cause any content on the page to change and the browser does not refresh.
  • history.replaceStateThe usage andhistory.pushStateThe difference between them ishistory.pushStateIs to add a routing record to the routing stack, andhistory.replaceStateIs to replace the current route with a new route.
  • window.onpopstateEvents listen for events that are triggered when the user clicks the browser’s forward and back buttons.

The window. onpopState event can only listen for forward and backward events. The window. onpopState event is not raised if you call history.pushState or history.replaceState in your code to change the URL.

Now look at the following JS code. The jump function is triggered when the user clicks the A TAB. Call history.pushState({}, “”,path) inside the function. The browser URL changes, but the page content has not changed. It’s only later when the Render function is executed that the actual content of the page starts to change.

The Render function dynamically changes the contents of the app container according to the different jump paths, thus simulating the effect of clicking on different paths as if the page had jumped.

Function jump(path){history.pushState({}, "",path); render(path); } function render(path){var app = document.getelementbyid ("app"); Switch (path){case "/a": app. InnerText = "page A"; break; Case "/b": app. InnerText = "page b"; break; Case "/c": app. InnerText = "page c"; break; Default: app.innerText = "other content "; Onpopstate = function(event) {const path = location.pathname; render(path); };Copy the code

The effect is as follows:

From the above case, we can summarize the implementation principle of front-end routing.

  • Make in some wayurlChange. This approach may be calledHTML5 history APIIt can also be clicking forward or backward or changing the routehashBut either way, it doesn’t cause a browser refresh, just a simple oneurlChange occurs.
  • Listen to theurlAfter the change, according to the different path to get the render content, and then fill the content todivIn the container. From the above case, listenurlThe change of the general in two places, the first is inwindow.onpopstateThe second of the wrapped callback functions is executedhistory.pushStateorhistory.replaceStateThe back of the.

The same is true for vue-router’s underlying logic, such as

Similar to the a tag in the example above, the bottom line isto change the URL using history.pushState or history.replaceState(when routing mode is set to ‘history’).
is equivalent to the app container element above, listening for different paths and then fetching the corresponding page component based on the path value and rendering it.
,>

Path analysis

The existing page templates are as follows: Router-link and router-view are global components that are commonly used in development.

<template>
  <div>
       <router-link :to="{ path: '/home' }">Home</router-link>
       <router-view/>
  </div>
</template>
Copy the code

Router-link is a jump link, and its source code will be explained later. Let’s assume that the inside of the component jumps by getting the path property in to. Following the principle of front-end routing described above, the first step of route hopping isto simply change the URL through the HTML5 History API, which can be done inside the router-link component.

Step 2 After listening for URL changes, need to get render content according to path. In the current case, the path corresponds to /home, so the rendered content can be easily found in the routing configuration table, such as the following configuration.

const routes = [
	  {
	    path: '/home',
	    component: Home
	  },
	  {
	    path: '/login',
	    component: () => import(/* webpackChunkName: "about" */ '../views/Login/Login.vue')
	  }
  ]
Copy the code

We can introduce the Routes array in the routing file and iterate over whether the path and /home match. Once the match is successful, we know that the content to render is the home component.

The above case is one of the simplest, because in practice we have nested arguments and dynamic parameters, and just using the above judgment method cannot meet the daily development requirements.

Let’s look at how the source code handles path matching. The source code creates a VueRouter constructor in which all the implementation logic for routing is written.

// page call const router = new VueRouter({mode: Var VueRouter = function VueRouter (options) {this.app = null; this.apps = []; this.options = options; //options correspond to the arguments passed in the previous page call this.beforeHooks = []; this.resolveHooks = []; this.afterHooks = []; this.matcher = createMatcher(options.routes || [], this); var mode = options.mode || 'hash'; this.mode = mode; switch (mode) { case 'history': this.history = new HTML5History(this, options.base); break case 'hash': this.history = new HashHistory(this, options.base, this.fallback); break case 'abstract': this.history = new AbstractHistory(this, options.base); break default: if (process.env.NODE_ENV ! == 'production') { assert(false, ("invalid mode: " + mode)); }}};Copy the code

The VueRouter constructor above does two main things.

  • First, it will be based onmodeConfigure the corresponding of different buildshistoryObject,historyAn object is an actor who performs specific routing operations.
  • The second thing it does is callcreateMatcherThe function is generatedthis.matcherObject… thisthis.matcherIs to solve the path matching problem we encountered above.

Let’s take a look inside createMatcher to find the component to render based on the path.

CreateMatcher implementation

Suppose the developer configured the routing table as follows, which is passed into the createMatcher function below for execution.

Const routes = [{path: '/home', Component: home, // home, children: [{path: 'list', name:"list", Component: list // list page}]}, {path: '/login', name:"login", Component: login // login page}]Copy the code

If the user accesses /home/list, it is obvious that the matching routing component should be list, but the data structure above is not quick and easy to find the routing component that matches the path.

Therefore, the createRouteMap function transforms the routes tree array, which makes it easier to find the matching component. After executing createRouteMap(Routes), three data are returned, and the transformed data structure is as follows:

pathList = ["/home","/home/list","/login"]; pathMap = { "/home":{ path: "/home", name: "home", regex: /^\/home(? : \ /? ) = $)? $/ I, parent: undefined, components: {default: {... }}, / / the corresponding page components Home meta: {}}, "/ Home/list" : {path: "/ Home/list", meta: {} name: "a list", the parent: {path: "/ Home",... }, // regex:/^ /home /list(? : \ /? ) = $)? $/ I, components: {default: {... }}, // corresponding page component List}, "/login":{... }} nameMap = {// this is the same data structure as above, except that name is used as key "home":{... }, "list":{... }, "login":{... }}Copy the code

What createRouteMap does below is convert the routing table routes into the data structure above. When we look at the characteristics of the above three data points, there are three key attributes.

  • parentAttribute indicates whether the current route has a parent route. If every subroute is usedparentAttribute stores the parent route, so that no matter how deep the nested path is, it passes through a child routeparentProperty looks up until all of its ancestor routes are found, which will be used later when rendering nested routines.
  • regexIs a regular expression dynamically generated based on the current configured path. How do I determine whether the current access path matches this route? Match the access path with the re. Using regular matching can also solve another problem, such aspathConfigure to dynamic form/detail/:id, then the access path is required if/detail/7Is also able to match the route, and its re is/^\/detail\/((? : [^ \] / +)? (?) : \ /? ) = $)? $/iIt matches the access path.
  • componentsIs an object that contains onedefaultProperty corresponds to the page component to render.

Now that you have converted the static configuration of routes to the above three data structures, it is much easier to find matching routes based on access paths. The matching logic is written in the match function below.

function createMatcher ( routes, router ) { const { pathList,pathMap,nameMap } = createRouteMap(routes); function addRoutes (routes) { createRouteMap(routes, pathList, pathMap, nameMap); } function match ( raw, currentRoute, redirectedFrom ) { var location = normalizeLocation(raw, currentRoute, false, router); var name = location.name; If (name) {// Match according to route name... } else if (location.path) {// Match location.params = {}; for (var i = 0; i < pathList.length; i++) { var path = pathList[i]; var record$1 = pathMap[path]; if (matchRoute(record$1.regex, location.path, location.params)) { return _createRoute(record$1, location, redirectedFrom) } } } // no match return _createRoute(null, location) } return { match: match, addRoutes: addRoutes }Copy the code

We mainly look at the routing by path (name matching can be studied by myself), it will directly through the one-dimensional array pathList, fetching each path value.

Then through the path value in pathMap to find out the corresponding route configuration, take out the regular expression regex and access path match, if the regular verification is successful, it means that the successful acquisition of the route configuration Record $1, and then pass it into the _createRoute function to generate a page to be displayed on the route.

The createMatcher function returns two arguments after execution.

  • The first is thematchThe routing object () function matches the access path and the routing configuration table one by one, and returns the correct routing object.
  • The second is theaddRoutesDelta function, this function can go to deltapathList .pathMap andnameMap Continue adding routing information. Officially providedthis.$router.addRoutesThis is where the dynamic routing comes from.

Back to our original question of how to find a matching routing object based on the access path. The most important thing is to be compatible with the nested arguments and dynamic parameters that developers might use to write routing tables. Finally, the match function returned by createMatcher can solve this problem.

There are two details we haven’t covered in the entire path resolution process.

  • One is thematch Why use a function that finally matches a route configuration_createRouteFunction creates a new route and returns the matched route configuration.
  • The second is something we already knowcreateRouteMapThe static routing table is converted topathList .pathMap andnameMap These three data structures, but how does it actually work inside?

_createRoute implementation

Record, the route object that passes the regular check, is passed into the following createRoute to execute in the match function. In this function, a new route object is created and the original route configuration information is added one by one, but one attribute matched needs to be noted.

function createRoute ( record, location, redirectedFrom, router ) { var stringifyQuery = router && router.options.stringifyQuery; var query = location.query || {}; try { query = clone(query); } catch (e) {} var route = { name: location.name || (record && record.name), meta: (record && record.meta) || {}, path: location.path || '/', hash: location.hash || '', query: query, params: location.params || {}, fullPath: getFullPath(location, stringifyQuery), matched: record ? formatMatch(record) : [] }; return Object.freeze(route); // Disable route modification}Copy the code

Matched property stores the configuration object of all ancestor routes, which will be used in the later interface rendering nested patterns. It is obtained via the formatMatch function.

The formatMatch function creates an array RES that iterates over the parent of the route and stores it in the RES. In addition to the basic configuration information, the returned route object also stores the configuration objects of all the ancestral routes in the matched attribute

function formatMatch (record) {
  var res = [];
  while (record) {
    res.unshift(record);
    record = record.parent;
  }
  return res
}
Copy the code

CreateRouteMap implementation

Routes is a static routing table written by the developer. Finally, the createRouteMap function returns the pathList, pathMap, and nameMap.

The createRouteMap function creates three variables and passes them to addRouteRecord to execute. This shows that the actual data conversion logic is all in the addRouteRecord function.

CreateRouteMap separately handles the path:”*” case by moving * to the end of the pathList array. Because path:”*” corresponds to the routing configuration of the regular expression is /^((? : (. *))? : \ /? ) = $)? $/ I, it matches all normal paths, so put it last to match. Only if none of the previous routes are matched, this scenario is a common 404 page.

function createRouteMap ( routes, oldPathList, oldPathMap, oldNameMap ) { var pathList = oldPathList || []; var pathMap = oldPathMap || Object.create(null); var nameMap = oldNameMap || Object.create(null); routes.forEach(function (route) { addRouteRecord(pathList, pathMap, nameMap, route); }); for (var i = 0, l = pathList.length; i < l; i++) { if (pathList[i] === '*') { pathList.push(pathList.splice(i, 1)[0]); // insert into the end of the array l--; i--; } } return { pathList: pathList, pathMap: pathMap, nameMap: nameMap } }Copy the code

The internal code of the addRouteRecord function is as follows. Record is a re-created route configuration, which has several properties of interest.

  • path: pathNot from the originalrouteDirectly, but throughnormalizePathThe purpose is to exist in the case of nested routines, the subpathpathAble to route to the ancestorpathSplice and return.
  • regex: compileRouteRegexFunction for each differencepathDynamically generates a matching regular expression.
  • parent:parentProperty records the parent route. We’ll see that in the code belowroute.childrenYes No. If yes, sub-routes exist. then route.childrenTraversal loop, each sub-route is called recursivelyaddRouteRecordFunction, which will be called by its childrenrecordAs aparentSo when recursive calls are made, the child createsrecord Records of theparentIt’s going to be worth something.
function addRouteRecord ( pathList, pathMap, nameMap, route, parent, matchAs ) { var path = route.path; var name = route.name; var pathToRegexpOptions = route.pathToRegexpOptions || {}; var normalizedPath = normalizePath(path, parent, pathToRegexpOptions.strict); if (typeof route.caseSensitive === 'boolean') { pathToRegexpOptions.sensitive = route.caseSensitive; } var record = { path: normalizedPath, regex: compileRouteRegex(normalizedPath, pathToRegexpOptions), components: route.components || { default: route.component }, instances: {}, name: name, parent: parent, matchAs: matchAs, redirect: route.redirect, beforeEnter: route.beforeEnter, meta: route.meta || {}, props: route.props == null ? {} : route.components ? route.props : { default: route.props } }; if (route.children) { route.children.forEach(function (child) { var childMatchAs = matchAs ? cleanPath((matchAs + "/" + (child.path))) : undefined; addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs); }); } if (! pathMap[record.path]) { pathList.push(record.path); pathMap[record.path] = record; } if (route.alias ! == undefined) { var aliases = Array.isArray(route.alias) ? route.alias : [route.alias]; for (var i = 0; i < aliases.length; ++i) { var alias = aliases[i]; if (process.env.NODE_ENV ! == 'production' && alias === path) { warn( false, ("Found an alias with the same value as the path: \"" + path + "\". You have to remove that alias. It will be ignored in development.") ); // skip in dev to make it work continue } var aliasRoute = { path: alias, children: route.children }; addRouteRecord( pathList, pathMap, nameMap, aliasRoute, parent, record.path || '/' // matchAs ); } } if (name) { if (! nameMap[name]) { nameMap[name] = record; } else if (process.env.NODE_ENV ! == 'production' && ! matchAs) { warn( false, "Duplicate named routes definition: " + "{ name: \"" + name + "\", path: \"" + (record.path) + "\" }" ); }}}Copy the code

Page rendering

The path resolution process has been introduced above, summed up to do such a thing. Click a
to pass the link to the match function of the Matcher object in VueRoute. Once it gets the link, it goes to pathList, pathMap The match function returns a route object that contains some basic configuration information :path,name,meta,params, etc. It also contains a particularly important attribute, matched, which holds the configuration information for the current and all ancestor routes.

For example, in the example above, the route data returned from /home/list is as follows:

{ fullPath: "/home/list" hash: "" matched: Array(2) 0: {path: "/home", regex: /^\/home(? : \ /? ) = $)? $/ I, components: {... }, the instances: {... }, name: "home",... } 1: {path: "/home/list", regex: /^\/home\/list(? : \ /? ) = $)? $/ I, components: {... }, the instances: {... }, name: "a list",... } meta: {} name: "list" params: {} path: "/home/list" query: {} }Copy the code

Now back to the original problem, click on
to get the route object, whose matched property holds the component to be rendered.

<template>
  <div>
       <router-link :to="{ path: '/home' }">Home</router-link>
       <router-view/>
  </div>
</template>
Copy the code

Router-link and router-View are two different components, so how can router-Link pass data to router-View even if it knows what the component to render is, and how can it trigger the router-View component to re-render?

Router-link does not communicate directly with router-View, but both can get the root component instance of the Vue. If router-link passes data to the root component instance and changes the reactive state of the root component, then the render function of all the child components is reexecuted.

Router-view is no exception. When it executes the render function, it takes the route object stored on the root component and renders the component content normally.

Now take a look at how vue-Router source code is implemented step by step. This is what we do when we initialize the application and create the routing object.

Vue.use(VueRouter); Const router = new VueRouter({mode: 'history', routes}) new Vue({router, render: h => h(App) }).$mount('#app')Copy the code

Vue. Use (OB) This API is used to install plug-ins for Vue. It usually calls the OB object install to complete the plug-in installation. Next, let’s focus on VueRouter’s install method.

The install argument Vue is a constructor of Vue, not an instance object. Source code to Vue mixin inside added a life cycle hook function beforeCreate. This will mean that all instance objects created using the current Vue constructor will run the beforeCreate hook function during creation.

var _Vue; Function install (Vue) {if (install.installed && _Vue === Vue) {return} // Ensure that the plug-in is installed only once install.installed = true; _Vue = Vue; var isDef = function (v) { return v ! == undefined; }; Vue.mixin({ beforeCreate: function beforeCreate () { if (isDef(this.$options.router)) { this._routerRoot = this; this._router = this.$options.router; this._router.init(this); Vue.util.defineReactive(this, '_route', this._router.history.current); } else { this._routerRoot = (this.$parent && this.$parent._routerRoot) || this; } }, destroyed: function destroyed () { ... }}); Object.defineProperty(Vue.prototype, '$router', { get: function get () { return this._routerRoot._router } }); Object.defineProperty(Vue.prototype, '$route', { get: function get () { return this._routerRoot._route } }); Vue.component('RouterView', View); Vue.component('RouterLink', Link); }Copy the code

The root instance will also run the beforeCreate function during the creation of the new vue instance. For this function, the router object is passed to the root instance.

Each vUE instance created in the vUE source code assigns a configuration object to $options. If this.$options. Router exists when the beforeCreate function is executed, this must be the root instance. It then assigns the Router object created by the VueRouter to the _router attribute of the root instance and executes init.

DefineReactive (this, ‘_route’, This._router.history.current), which creates a reactive state _route on the root instance, which means that if anyone modiates the _route of the root instance, the render function of all the child components can be reexecuted.

Prototype has two values, $router and $route, which point to the router created by new VueRouter and the responsive state _route on the root instance. We often call this.$route. push and this.$route.params to get parameters in child components. Because this.$router and this.$route are both mounted on Vue prototype objects, all Vue instances can be called directly.

New Vue({router, render: h => h(App)}).$mount('# App ')Copy the code

VueRouter init implementation

Now let’s take a look at the initialization operations this._router.init(this) does. When the page is first loaded, the Vue root instance is passed as an argument to the init function for execution.

VueRouter’s constructor, described above, creates a history object when instantiated for parameter passing and jump between routes.

Look at the code below, get the history object, determine which mode the history object was created from, and then call the transitionTo function to jump.

VueRouter.prototype.init = function init (app) { var this$1 = this; this.apps.push(app); if (this.app) { return } this.app = app; var history = this.history; If (history instanceof HTML5History) {/ / history model history. TransitionTo (history) getCurrentLocation ()); } else if (history instanceof HashHistory) {// Hash mode... } history.listen(function (route) { this$1.apps.forEach(function (app) { app._route = route; }); }); };Copy the code

TransitionTo is the jump function provided by the history object. During project initialization, a jump to the current path is required, which is equivalent to a jump to the/root path if the current page is the home page.

Unlike normal link jumps, transitionTo has a powerful capability to ensure that the _route of the root instance is updated to the latest route object at the end of each execution, thus rerendering the page.

After init, we call listen, which looks like a listener. History. listen is a function that uses observer mode to store the previous function wrapped in listen onto the cb property of the history object. Once this.history.cb(new_route) is executed, you can expect the Listen callback to be triggered. This $1.apps contains the root component instance, and the page will be re-rendered once the latest route object is assigned to the responsive state of the root instance.

TransitionTo causes the page to be rerendered because this.history.cb(new_route) was called internally.

History.prototype.listen = function listen (cb) {
  this.cb = cb;
};
Copy the code

TransitionTo implementation

Here comes the familiar this.router.match function, which we spent a lot of time on in section 2 above. The match function finds the appropriate route object based on the desired location and returns it.

As soon as the route argument is passed to this. ConfirmTransition, it doesn’t jump immediately, but makes a bunch of intercept judgments, which is where the route guard comes in.

Routing guard is the essence of vue-Router source code, which will open a separate section focusing on the implementation of this. ConfirmTransition internal routing guard.

This. ConfirmTransition has a large number of routing guards wrapped in it, and if it passes the routing guard’s confirmations, it will be executed ConfirmTransition this. ConfirmTransition second argument (the callback function), which means that the routing guard has been cleared to jump to the current route object route.

History.prototype.transitionTo = function transitionTo ( location, onComplete, onAbort ) { var this$1 = this; Var route = this.router. Match (location, this.current); // This.router. This.confirmTransition(route, function () {this$1.updateroute (route); onComplete && onComplete(route); // Jump to the successful callback function this$1.ensureURL(); // Fire ready CBS once if (! this$1.ready) { this$1.ready = true; this$1.readyCbs.forEach(function (cb) { cb(route); }); } }, function (err) { if (onAbort) { onAbort(err); } if (err &&! this$1.ready) { this$1.ready = true; this$1.readyErrorCbs.forEach(function (cb) { cb(err); }); }}); };Copy the code

Let’s look at the implementation of the callback function this$1.updateroute (route).

The cb function of the history object is executed inside the function, and as we analyzed above, the reactive state _route of the root component instance is updated by calling history.cb(route), causing all the child component render functions to be re-executed.

History.prototype.updateRoute = function updateRoute (route) {
  var prev = this.current;
  this.current = route;
  this.cb && this.cb(route);
  this.router.afterHooks.forEach(function (hook) {
    hook && hook(route, prev);
  });
};
Copy the code

Now we can walk through the process, history.transitionto (location) is the function that performs the jump. It first passes the path Location to the match function to get the matching route object route. This $1.updateroute (route) is used to assign the route object to the responsive state _route of the root component to start the page rendering.

Let’s now look at the implementation of the
component. Its click event is bound to a handler, which calls the Push or replace methods of the Router object.

var Link = { name: 'RouterLink', props: { to: { type: toTypes, required: true }, tag: { type: String, default: 'a' }, event: { type: eventTypes, default: 'click' } }, render: function render (h) { var this$1 = this; var router = this.$router; var current = this.$route; var ref = router.resolve( this.to, current, this.append ); var location = ref.location; var route = ref.route; var href = ref.href; . var handler = function (e) { if (guardEvent(e)) { if (this$1.replace) { router.replace(location, noop); } else { router.push(location, noop); }}}; var on = { click: guardEvent }; if (Array.isArray(this.event)) { this.event.forEach(function (e) { on[e] = handler; }); } else { on[this.event] = handler; }... return h(this.tag, data, this.$slots.default) } };Copy the code

Take the push method as an example. The push method of the Router object calls the push method of the History object. The push method of the history object also calls the transitionTo function.

VueRouter.prototype.push = function push (location, onComplete, onAbort) { var this$1 = this; if (! onComplete && ! onAbort && typeof Promise ! == 'undefined') { return new Promise(function (resolve, reject) { this$1.history.push(location, resolve, reject); }) } else { this.history.push(location, onComplete, onAbort); }}; HTML5History.prototype.push = function push (location, onComplete, onAbort) { var this$1 = this; var ref = this; var fromRoute = ref.current; this.transitionTo(location, function (route) { pushState(cleanPath(this$1.base + route.fullPath)); handleScroll(this$1.router, route, fromRoute, false); onComplete && onComplete(route); }, onAbort); };Copy the code

All jump operations run the transitionTo function, which can update the status of the root component instance and restart the page rendering. Second, it contains the logic of the route guard, and each jump must pass the verification of the route guard.

RouterView implementation

After the transitionTo function is successfully executed, the state _route of the root component instance has been assigned to the latest routing object, and the render functions of all child components will be re-executed.

The render function in the

component is also reexecuted, which takes the latest routing object from the root instance and starts rendering the page content.


render

render

render



{ fullPath: "/home/list" hash: "" matched: Array(2) 0: {path: "/home", regex: /^\/home(? : \ /? ) = $)? $/i, components: {default:{... }}, the instances: {... }, name: "home",... } 1: {path: "/home/list", regex: /^\/home\/list(? : \ /? ) = $)? $/i, components: {default:{... }}, the instances: {... }, name: "a list",... } meta: {} name: "list" params: {} path: "/home/list" query: {} }Copy the code

In the RouterView function, functional: true is set, so ref. Parent is the current
virtual DOM.

When we are developing multi-level nested routines,
will also write more than one, which will be distributed among different component templates. Each
is only responsible for rendering its own part.

As can be seen from the data structure of the routing object above,matched stores two elements, one of which is the parent route name:home and the other is the child route name:list. There will also be two
in the page template, one render parent element and one render child element.

So the following code executes while (parent && parent._routerRoot! == parent) loop, the current virtual DOM node is continuously searched until it finds the root node, in order to determine the current
level and the corresponding value depth.

Once depth is obtained, we can call route.matched[depth] to retrieve the current
Call matched.components[name] from the route object to get the page component (name is default), then you can render the page component content smoothly.

var View = {
  name: 'RouterView',
  functional: true,
  props: {
    name: {
      type: String,
      default: 'default'
    }
  },
  render: function render (_, ref) {
    var props = ref.props;
    var children = ref.children;
    var parent = ref.parent;
    var data = ref.data;

    // directly use parent context's createElement() function
    // so that components rendered by router-view can resolve named slots
    var h = parent.$createElement;
    var name = props.name;
    var route = parent.$route;
    var cache = parent._routerViewCache || (parent._routerViewCache = {});

    // determine current view depth, also check to see if the tree
    // has been toggled inactive but kept-alive.
    var depth = 0;
    var inactive = false;
    while (parent && parent._routerRoot !== parent) {
      var vnodeData = parent.$vnode && parent.$vnode.data;
      if (vnodeData) {
        if (vnodeData.routerView) {
          depth++;
        }
        if (vnodeData.keepAlive && parent._inactive) {
          inactive = true;
        }
      }
      parent = parent.$parent;
    }
    data.routerViewDepth = depth;

    var matched = route.matched[depth];

    var component = cache[name] = matched.components[name];
    
    ... //省略

    return h(component, data, children)
  }
};
Copy the code

Stern said

This article has sorted out the core workflow and source code of VUe-Router. The next summary will focus on the implementation of route guard in Vue-Router.