Vue Router Source Overview Case Analysis

Source code This thing is that there is no immediate effect on the actual work. If you look like the strong articles, you can use it in the actual project, what effect is generated, the source code is a The process of subtlety, its idea, design mode, code structure, etc. may not immediately achieve the achievement (or very small), but in the future, there is no sound in the future, you don’t even feel No, this process

In addition, excellent source code cases, such as Vue, React, the content is relatively large, not three five ten art art articles can be finished, but also written It is difficult to write clear, and it is quite a waste of time, and if it is just to analyze one of the points, such as Vue response, similar articles are already enough, there is no need to repeat

So I didn’t specifically Write the source code analysis, just look, but recently, I haven’t read the source code of Vue-router, I found this plugin level, compared to the Vue level, the logic is simple and clear, there is no Multiple channels, the amount of code is not much, but the concepts contained are very refined, worthy of writing, of course, the text is just an overview, not a line of code to analyze the past, details still have to see it.

Vue.use The VUE plugin must be registered via Vue.use, the code of Vue.use is located in the Vue source of SRC / Core / Global-API / USE. In the JS file, there are two main functions of this method:

Caches the registered components to avoid registering the same plug-in multiple times
IF (PLUGIN)> -1) {Return THIS}
   Call the INSTALL method of the plugin or directly run the plugin to achieve Plug-inInstall 
i (TypeOf plugin.install === ‘function’) {plugin.install.Apply (plugin, args)} else IF (typeof plugin === ‘function’) {plugin.apply (null, args)}

  • Routing installation
VUE -Router’s install method is mainly in the src / install.js of the Vue-Router source, which is mainly entered into the Before.minxin, and registers the Router-View and Router-Link Components through Vue.minxin. The Router-View and Router-Link Components
   // src / install.jsvue.mixin ({BeforeCreate () {i (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 } RegisterInstance (this, this)},Destroyed () {registerinstance (this)}}) ... // Global registration `Router-View` and` Router-link`mmmue Vue.component ('RouterLink', Link) 
Routing mode

Vue-router supports three routing modes: Hash, History, Abstract, where Abstract is Routing mode used in non-browser environments, such as WeEx

routing inside the route to determine the external specified routing mode, such as the current environment is a non-browser environment, no matter what kind of MODE, final It will be mandatory to specify as Abstract, if it is determined that the current environment does not support HTML5 HISTORY, it will eventually be downgraded to haveh mode

  // src / index.jslet mode = Options. Mode || 'Hash'this.fallback = MODE ===' History '&&! SupportspushState && Options.fallback! == falseif (this.fallback) {mode =' hash '} if (! inbrowser) {Mode =' Abstract  
Finally, the corresponding initialization operation

// SRC / INDEX will be performed on the required MODE. Jsswitch (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}`)}}

   The nested routing 

// SRC / CREATE-ROUTE is analyzed by recursion -map.jsfunction addRouteRecord (Pathlist: Array
, Pathmap: Dictionary , NameMap: Dictionary
, Route: Routeconfig, Parent ?: RouteRecord, matchas ?: string {... route.children.ForeAach (CHILD => {const milkmatchas = matchas? CleanPath (`$ {matchas} / $ {child.path}`): undefined addrouteRecord (Pathlist, Pathmap, NAMemap, child, record, childmatchas}) ...}   After completion, the parsing route will be recorded through the form of Key-Value. If a plurality of routes (PATH) routing are declared, only the first one will work, the back will be ignored 

// src / create-route-map .jsif (! PathMap [Record.Path]) {pathlist.push (record.path) pathmap [record.path] = record}

, for example, the following routing configuration, routing / bar only matches Bar1, BAR2 configuration will be ignored
   const routes = [{pat: '/ foo', Component: foo}, {Path: ' / bar ', component: bar1}, {path:' / bar ', component: bar2},];    Routing switching 
When accessing a URL, Vue-Router will match the path to create a Route object, which can be accessed through this. $ Route

 // SRC / UTIL / ROUTE.JSCONST ROUTE: ROUTE = {Name: location.name || (Record && Record.name), Meta: (Record && Record.meta) || {}, path: location.path || '/', have: location.hash || '', query, params: location.params || {}, fullpath: getFullPath (location, stringify), matched: Record? formatmatch (record): []}   TRANSTO () in the SRC / History / Base.js source file is the core method of routing switching 

TransitionTo (Location: Rawlocation, oncomplete ?: function, onabort ?: function) {const route = this.router.match (location, this.current) this.confirmtransition (route, () => {…}
The route switching method of the routing instance is based on the PUSH method of routing, such as Hash mode based on this method:
  // SRC / History / Hash.jspush (Location: RawLocation, Oncomplete ?: function, onabort ?: function) {const {current: prote} = this // Use TransitionTo Method THIS.TRANSITIONTO (Location, route => {pushhash (troute.ffullpath) Handlescroll (this.Router, Route, Fromroute, False) Oncomplete && Oncomplete (route)}}}}}}}  

TransitionTo The method is updated internally by an asynchronous function queue to update the switching route, perform asynchronous callbacks through the Next function, and perform the corresponding hook function (ie navigation guard) Beforeeach, BeforeRouteUteEnter, BeforouteereAve Save the corresponding routing parameters through the queue:

// src / history / base.jsconst Queue: array
 = [] .concat // in-component leave guards extractLeaveGuards (deactivated), // global before hooks this.router.beforeHooks, // in-component update hooks extractUpdateHooks (updated), // in-config enter guards activated.map (m => m .beforenter, // async components resolveasynccomponents (ActiVated))   
The asynchronous function queue is initiated by Runqueue in a recursive callback manner.Perform:
// src / history / base.js // Asynchronous callback function Runqueue (Queue, Iterator, () => {const postetorcbs = [] const isvalid = () => this.current === route // wait until async components are resolved before // extracting in-component enter guards const enterGuards = extractEnterGuards (activated, postEnterCbs, isValid) const queue = enterGuards.concat (this.router .resolvehooks) // Recursively execute Runqueue (Queue, Iterator, () => {if (this.pending! == route) {return abort ()} this.pending = null oncomplete (route) if (this.Router.App ) {this.Router.app. $ nexttick (() => {Postentercbs.Foreach (CB => {CB ()})}}})}

 With NEXT, the navigation guard iteration is made, so if navigation hook functions are explicitly declared in the code, you must call next (), otherwise the callback does not execute, the navigation will not continue   
// SRC / History / base.jsconst iterator = (hook: NavigationGuard, next) => {… hook (route, current, (to: any) => {…} else {// confirm transition and pass on the value next (to)}}) …}

 synchronization routing   
at the time of route switching, vue-router calls push, go and other methods to achieve the view address url synchronization

address field url and as the view of

routing switching when a button like click on the page operation, vue-router will be maintained by changing the synchronized view and window.location.href url, e.g. hash route switching modes:
   // src / history / hash.jsfunction pushHash (path) {if (supportsPushState) {pushState (getUrl (path))} else {window.location.hash = path}}  

the above code, the first current detection whether the browser supports html5 the History API, if they support calling this API to modify href otherwise be directly window.location.hash assignment history with this principle the same, but also use the History API

view address bar url synchronization
   When clicking on the browser's advancement, the view can be achieved, because when the route is initialized, the event listener for the browser is retracted backwards 
The following is the event monitoring of Hash mode:

// src / history / hash.jssetuplisteners () {… WINDOW.ADDEVENTLISTENER (SupportsPushState? ‘Popstate: ‘hashchange’, () => {const current = this.current if (! EnsuRESLASH ()) {Return} this.transitionTo (GetHash (), route => {if (supportsscroll) {handlescroll (this.Router, Route, Current, true)} {replacehash (route.ffullpath)}}}}}
   
History mode Similar to this:

// src / history / html5.jswindow.addeventlistener (‘popstate’, e => {const current = this.current // Avoiding first `PopState` Event Dispatch in Some Browsers Butfirst / / History Route Not Updatedsince async guard at the same time. const location = getLocation (this.base) if (this.current === START && location === initLocation) {return} this.transitionTo (location, route => {if (supportsScroll) {Handlescroll (Router, Route, Current, True)}})}})

Whether it is HASH or History, transitionTo is called by listening to the event, thus Realization of routing and view of unified

In addition, when the page is first accessed, the routing is initialized, if it is a Hash mode, the URL is checked, and if the URL discovered, the URL is found without the # character, then It will be automatically appended, such as the initial access http: // localhost: 8080 this URL, Vue-Router automatically replaces the route management after HTTP: // localhost: 8080 / # /, is convenient:

// src / history / hash.jsfunction ensureSlash (): boolean {const path = gethash () IF (path.charat (0) === ‘/’) {return true} Replacehash (‘/ ‘+ Path) Return False}

ScrollBehavior
  When jump from a routing / A to another routing / b, if the scroll behavior of the scroll bar is performed in the page of the routing / A, the page jumps to / B, the browser scroll bar will be found. Location and / A (if / b can scroll), or refresh the current page, the browser's scroll strip position is still constant, and will not return directly to the top and if it is clicked by clicking the browser, the back button When controlling routing, the department browser (such as WeChat) scroll bar automatically returns to the top, the position of scrolltop = 0 is the browser's default behavior, if you want to customize the scroll when you want to customize the page The position of the strollbehavior, the Vue-Router's options  
when the routing is initialized, Vue-Router will monitor the switching event of the route, and part of the listening logic is the location used to control the browser scroll bar. :

// src / history / haveh.jssetuplisteners () {… if (supportsscroll) {// Event control setupscroll ()} for browser scroll bars …}

This set method defines that in src / util / scroll.js, this file is specifically used to control the position of the scroll bar, and switch events by listening to the routing Rolling bar position control:

// src / util / scroll.jswindow.addeventListener (‘popstate’, e => {SAVESCROLLPSITION () IF (E.State && E.State.key) {setStateKey (E.State.Key)}}

The rolling strip position of the routing switch can be customized by scrollbehavior, and the source code on the Github of Vue-Router, there is associated example, source location location in Vue-router / examples / scroll-behavior / app.js
  Router-View & router-link  
Router-View and Router-Link These two Vue-Router’s built-in components, located under src / components

Router-View
Router-View is a functional component (without response data), no instance (without this context), which acquires the corresponding components via routing matching The instance is dynamically generated by the H function. If the current routing does not match to any components, rendering an annotation node
   
// Vue-router / src / components / view. JS … const matched = route.matched [depth] // render Empty node if no matched routeif (! matched) {cache [name] = null return h ()} const component = cache [name] = matched.components [ Name] … Return H (Component, Data, Children)

Triggered the Router-View re-render to render the new view, this triggered The action is when the Vue-Router initializes the init.Waiting:
 // src / install.jsvue.mixin ({BeforeCreate () {if (ISDEF (this. $ Options.Router) {THIS ._RouterRoot = this this._router = this. $ options.router this._router.init (this) // Trigger router-view rendering Vue.util.definereActive (this, '_route', this._router.history.current) ...})   
Turn this._route to a response-based data, this definereactive is defined in the Vue, which is used to turn data into a response. One way, source code in Vue / SRC / Core / Observer / Index.js, its core is to modify data from the Object.defineProperty method to modify the data of the data and setter:

Object .defineproperty (Obj, Key, {ENUMERABLE: TRUE, CONFIGURABLE: TRUE, GET: FUNCTION ReactivegetTer () {const value = getter? getter.call (obj): Val if (dep.target) {// Rely on Collection DEP. Depend () if (childob) {ChilDob.Dep.depend () f (array.isarray)}}} Return Value}}} Return Value}}} Return Value}}}} Return Value}}}} Return Value}}} Return Value}}} Return Value}}} Return Value}}}} Return Value} Response DEP.NOTIFY ()

When the route changes, the Router-View’s render function will be called. This function has accessed this._route data, too It is equivalent to calling this._route’s getter method, triggering relying on collection, establishing a Watcher, executing the _Update method, so that the page re-renamed

  // vue-router /src/components/view.jsrender (_, {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 Rendered by Router-View CAN Resolve Named Slots Const H = Parent. $ CREATEEELEMENT Const Name = Props.Name // Trigger Dependency Collection, Create Render Watcher Const RouTE = Parent. $ route ...}  
This render Watcher’s distribution update is also the call of STTER, located in src / index.js:

History.Listen (route => {this.apps.foreach ((app) => {// trigger setter app._route = route})
   Router-link 
When executing the render function, add Class to the rendered Active element based on the current routing state, so You can use this to the Active routing element setting style, etc .:

// src / components / link.jsrender (h: function) {… 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 …}

Router-Link default rendered element is tag, which will give this

Add HREF attribute values, and some events for monitoring to trigger routing, the default is Click event:

// Src / Components / Link.jsdata. ON = onData.attrs = {href}

In addition, you can customize the element-link rendered element tags by incoming TAG this PROPS:

   If the TAG value is not a, it will be recursively traversed by the child-link child, until a A tag is found, the event will eventually And the route assignment to this 
, if you do not find a tag, put the event and route on the Router-Link rendered itself:

IF THIS.TAG === ‘a’) {data.on = on data.attrs = {href}} else {// Find the first
child and apply listener and href // Findanchor is recursive throughout the middle Method const a = findanchor (this. $ Slots.default) …}}
   When these routing switchesWhen the event is called, the corresponding method is called to switch the route refresh view: 

// src / components / link.jsconst handler = E => {IF (GuardEvent (e) ) {if (this.replace) {// replace routing Router.Replace (location)} else {// push routing Router.push (location)}}}
   
Summary
As can be seen, the source code of Vue-router is very simple, more suitable for newcomers to read analysis

Source code, my understanding is not necessary It is necessary to specifically take time. As long as you read the document, you can use the API to achieve a variety of demands correctly, the emergence of the wheel is to actually develop, not used to toss the developer, pay attention , I don’t want to see it, I have time to see, even if I don’t understand the road, I have seen it, I will always have a harvest, such as when I am watching the Vue source code, I often see this similar to this.值 写法:
 // Vue / src / core / vdom / create-functional-component.js (clone.Data || (clone.Data = {}) ).   If it is, I usually write this for this logic: 

if (clone.data) {clone.data.slot = data.slot} else {clone.data = {slot:Data.slot}
  What is the difficulty or understanding of the first way to write, just getting used to the second way of writing, usually writing the code in the process of time I have written it without thinking, habits become natural, but when I see the first way of writing, I will take my head to think about it. It can be written. I used to knocking so many keyboards, so I have to see more. Outstanding source code, avoid adding the world to make the car, so that you can check the shortage, this is also the reason I think that the code REVIEW is more important, it is difficult to find problems, others may see it, this is a fascinator BAPCHE  
The above is the Vue Router source code overview to you, I hope to help everyone. If you have any questions, please leave a message, the small package will reply to everyone. Thank you very much for your support of Tumi Cloud Website!
© Copyright Notice
THE END
Just support it if you like
like0
share
comment Grab the couch

Please log in to comment