Source code this thing for the actual work actually has no immediate effect, not like those highly targeted articles after reading can be immediately applied to the actual project, what kind of effect, the role of source code is a subtle process, Ideas, design patterns, code structures, etc., may not be immediately (or very little) tangible, but will play out in a way that you don’t even feel

In addition, the excellent source case, for example vue, the react this, relatively large in capacity, not three five article ten eight article can finish, and it is hard to read write clearly, also is a waste of time, and if the analysis is one of the points, for example vue responsive, similar articles have more than enough, No need to repeat

Before so I didn’t write the source code analysis, just his look, but recent spare nothing to see the vue – the router source, found that the level of plug-in, compared the vue level of this framework, logic is clear, simple without so many road, not the amount of code, but contains the idea of such things is refined, Worth writing, of course, as the name suggests, just an overview, not a line by line code analysis of the past, the details of things or to see their own

vue.use

The vue plugin must be registered via vue.use. The vue. Use code is located in the SRC /core/global-api/use.js file of the vue source code.

  • Caches registered components to avoid registering the same plug-in multiple times
if (installedPlugins.indexOf(plugin) > - 1) {
  return this
}
Copy the code
  • Calling plug-ininstallMethod or directly run the plug-in to implement the plug-in’sinstall
if (typeof plugin.install === 'function') {
  plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
  plugin.apply(null, args)
}
Copy the code

Routing installation

The vue-router install method is located in the vue-router source code SRC /install.js. The vue.minxin method is used to mix beforeCreate and destroyed hook functions. And register router-View and router-link components globally

// src/install.js
Vue.mixin({
  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
    }
    registerInstance(this.this)
  },
  destroyed () {
    registerInstance(this)}})...// Register the router-view and router-link components globally
Vue.component('RouterView', View)
Vue.component('RouterLink', Link)
Copy the code

Routing patterns

Vue-router supports three routing modes: hash, history, and Abstract. Abstract is a routing mode used in non-browser environments, such as WEEX

For example, if the current environment is not a browser environment, whatever mode is passed in will be forcibly specified as abstract. If it is judged that the current environment does not support HTML5 History, the route will be degraded to hash mode

// src/index.js
let mode = options.mode || 'hash'
this.fallback = mode === 'history'&&! supportsPushState && options.fallback ! = =false
if (this.fallback) {
  mode = 'hash'
}
if(! inBrowser) { mode ='abstract'
}
Copy the code

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

// src/index.js
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

Route resolution

Through the recursive way to resolve the nested routine

// src/create-route-map.js
function addRouteRecord (pathList: Array
       
        , pathMap: Dictionary
        
         , nameMap: Dictionary
         
          , route: RouteConfig, parent? : RouteRecord, matchAs? : string
         
        
       ) {... route.children.forEach(child= > {
    const childMatchAs = matchAs
      ? cleanPath(`${matchAs}/${child.path}`)
      : undefined
    addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs)
  })
  ...
}
Copy the code

After the parsing is complete, the resolved routes are recorded in key-value pairs. Therefore, if multiple route mappings of the same path are declared, only the first one takes effect and the subsequent ones are ignored

// src/create-route-map.js
if(! pathMap[record.path]) { pathList.push(record.path) pathMap[record.path] = record }Copy the code

For example, in the following route configuration, route /bar matches only Bar1 and Bar2 is ignored

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

Routing switch

When accessing a URL, vue-Router matches the path to create a route object that can be accessed via this.$route

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

The SRC /history/base.js source file transitionTo() is the core method for route switching

transitionTo (location: RawLocation, onComplete? :Function, onAbort? :Function) {
  const route = this.router.match(location, this.current)
  this.confirmTransition(route, () => {
  ...
}
Copy the code

Route switching methods such as push and replace of route instances are based on this method. For example, push method in hash mode:

// src/history/hash.jspush (location: RawLocation, onComplete? :Function, onAbort? :Function) {
  const { current: fromRoute } = this
  // The transitionTo method is used
  this.transitionTo(location, route => {
    pushHash(route.fullPath)
    handleScroll(this.router, route, fromRoute, false)
    onComplete && onComplete(route)
  }, onAbort)
}
Copy the code

The transitionTo method internally updates the switch route in a queued mode, with the next function performing the asynchronous callback, BeforeEach, beforeRouteUpdate, beforeRouteEnter, beforeRouteLeave are executed in the asynchronous callback method

The corresponding route parameters are stored in the queue array:

// src/history/base.js
const queue: Array<? NavigationGuard> = [].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.beforeEnter),
  // async components
  resolveAsyncComponents(activated)
)
Copy the code

The runQueue initiates the execution of the asynchronous function queue in a recursive callback:

// src/history/base.js
// Async callback function
runQueue(queue, iterator, () => {
  const postEnterCbs = []
  const isValid = (a)= > 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)
  // Execute recursively
  runQueue(queue, iterator, () => {
    if (this.pending ! == route) {return abort()
    }
    this.pending = null
    onComplete(route)
    if (this.router.app) {
      this.router.app.$nextTick((a)= > {
        postEnterCbs.forEach(cb= > { cb() })
      })
    }
  })
})
Copy the code

The navigational guard callback iteration is done through next, so if the navigational hook function is explicitly declared in the code, next() must be called at the end, otherwise the callback will not execute and the navigation will not continue

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

Routing synchronization

During route switchover, vue-Router invokes methods such as push and Go to synchronize views and URLS

The address barurlSynchronization with the view

When clicking the button on the page for route switching, vue-Router will keep the view and URL synchronized by changing window.location.href, for example, route switching in hash mode:

// src/history/hash.js
function pushHash (path) {
  if (supportsPushState) {
    pushState(getUrl(path))
  } else {
    window.location.hash = path
  }
}
Copy the code

If so, call this API to modify href. Otherwise, assign window.location.hash to History. It also leverages the History API

View and address barurlThe synchronous

The view can also be synchronized when the forward and back buttons of the browser are clicked, because the forward and back event listeners for the browser are set when the route is initialized

The following is an event listener in hash mode:

// src/history/hash.js
setupListeners () {
  ...
  window.addEventListener(supportsPushState ? 'popstate' : 'hashchange', () = > {const current = this.current
    if(! ensureSlash()) {return
    }
    this.transitionTo(getHash(), route => {
      if (supportsScroll) {
        handleScroll(this.router, route, current, true)}if(! supportsPushState) { replaceHash(route.fullPath) } }) }) }Copy the code

The history mode is similar:

// src/history/html5.js
window.addEventListener('popstate', e => {
  const current = this.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(this.base)
  if (this.current === START && location === initLocation) {
    return
  }

  this.transitionTo(location, route => {
    if (supportsScroll) {
      handleScroll(router, route, current, true)}})})Copy the code

For both hash and history, the transitionTo method is called at the end of the event to unify the route and view

In addition, when a page is accessed for the first time and the route is initialized in hash mode, the URL will be checked. If no # character is found, the url will be appended automatically. For example, the url http://localhost:8080 is accessed for the first time. Vue-router will be automatically replaced with http://localhost:8080/#/ to facilitate route management:

// src/history/hash.js
function ensureSlash () :boolean {
  const path = getHash()
  if (path.charAt(0) = = ='/') {
    return true
  }
  replaceHash('/' + path)
  return false
}
Copy the code

scrollBehavior

When routing/a jump from one to the other routing/b, if to the routing/a page in the rolling behavior of the scroll bar, then the page jump to/b, will find that the browser’s scrollbar position as/a (if/b also can roll), or refresh the current page, the browser’s scrollbar position remains unchanged, Not directly back to the top And if it is by clicking on the browser’s forward and backward buttons to control the routing switch, is part of the browser (such as WeChat) scrollbar in routing switch will automatically return to the top, namely the position of the scrollTop = 0 This is the default browser behavior, if you want to customize pages when switching position of the scroll bar, The options of vue-router can be scrollBehavior

When the route is initialized, vue-Router listens for route switching events. Part of the listening logic is used to control the position of the browser scroll bar:

// src/history/hash.js
setupListeners () {
  ...
  if (supportsScroll) {
    // Enable event control for the browser scroll bar
    setupScroll()
  }
  ...
}
Copy the code

The set method is defined in SRC /util/scroll.js, which is specifically used to control the scrollbar position by listening for route switching events:

// src/util/scroll.js
window.addEventListener('popstate', e => {
  saveScrollPosition()
  if (e.state && e.state.key) {
    setStateKey(e.state.key)
  }
})
Copy the code

You can customize the scrollbar position for route switching by using scrollBehavior. The source code on Github of vue-Router contains relevant examples. The source code location is vue-router/examples/ scrollBehavior /app.js

router-view & router-link

Router-view and router-link are two built-in components of vue-Router. The source code is located under SRC/Components

router-view

Router-view is a stateless (no responsive data), instance-free (no this context) functional component. It obtains the corresponding component instance through route matching, dynamically generates the component through h function, and renders a comment node if the current route does not match any component

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

Router-view rerender each route switch triggers a router-view rerender to render a new view. This action is declared when vue-Router initializes init:

// src/install.js
Vue.mixin({
  beforeCreate () {
    if (isDef(this.$options.router)) {
      this._routerRoot = this
      this._router = this.$options.router
      this._router.init(this)
      // Triggers router-view rerendering
      Vue.util.defineReactive(this.'_route'.this._router.history.current)
      ...
})
Copy the code

Will enclosing _route through defineReactive into a response data, the defineReactive is defined in the vue, a method used to turn data into reactive, source in vue/SRC/core/observer/index. The js, The core is to modify the getters and setters of the data via object.defineProperty:

Object.defineProperty(obj, key, {
    enumerable: true.configurable: true.get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        // Do dependency collection
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {...// Notify the observer subscribed to the current data watcher to respond
      dep.notify()
    }
Copy the code

When the route changes, the router-view’s render function is called, which calls the this._route data, which is equivalent to calling the getter of this._route, triggering dependency collection, and setting up a Watcher. Execute the _update method to re-render the page

// vue-router/src/components/view.js
render (_, { 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.$createElement
  const name = props.name
  // Trigger dependency collection to create render Watcher
  const route = parent.$route
  ...
}
Copy the code

The render Watcher dispatch update, which calls the setter, is located in SRC /index.js:

history.listen(route= > {
  this.apps.forEach((app) = > {
    / / triggers the setter
    app._route = route
  })
})
Copy the code

router-link

Router-link adds class to the active element based on the current route state when executing the render function, so you can use this to style the active element:

// src/components/link.js
render (h: Function) {... const globalActiveClass = router.options.linkActiveClassconst 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
    ...
}
Copy the code

By default, router-link renders the tag, which adds an href value to the tag and listens for events that trigger a route switch. The default is the click event:

// src/components/link.js
data.on = on
data.attrs = { href }
Copy the code

Alternatively, you can customize the elements rendered by router-link by passing tag props:

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

If the tag is not a, the router-link element is recursively traversed until an A tag is found, and the event and route are assigned to the element. If no A tag is found, the event and route are placed on the router-Link rendered element itself:

if (this.tag === 'a') {
    data.on = on
    data.attrs = { href }
  } else {
    // find the first <a> child and apply listener and href
    // findAnchor is a recursive method for traversing child elements
    const a = findAnchor(this.$slots.default) ... }}Copy the code

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

// src/components/link.js
const handler = e= > {
  if (guardEvent(e)) {
    if (this.replace) {
      / / replace routing
      router.replace(location)
    } else {
      / / push routing
      router.push(location)
    }
  }
}
Copy the code

conclusion

As you can see, vue-Router source code is very simple, more suitable for novice reading analysis

Source this kind of thing, my understanding is that there is no need to make time to see, as long as you read the document, and skilled use of the API correctly implement various needs that should do, this is the appearance of the wheels for actual development services rather than for developers, note that I am not saying don’t go to see, have the time or want to see, even if can’t figure out which way way, For example, when I look at the vue source code, I often see an assignment like this:

// vue/src/core/vdom/create-functional-component.js
(clone.data || (clone.data = {})).slot = data.slot
Copy the code

If it was before, I would write something like this:

if (clone.data) {
  clone.data.slot = data.slot
} else {
  clone.data = {
    slot: data.slot
  }
}
Copy the code

There is not to say that the first written what difficulty or look not to understand, just get accustomed to the second kind of writing, writing code at ordinary times naturally without thinking in the process of writing, the habit is a second nature, but when I saw the first one when I have written a racquet head thought originally so can also be written, white knocked at the keyboard, so many times before So nothing to look at other people’s excellent source code, to avoid indulging in their own world behind closed doors, so as to check and fill gaps, this is also I think the code review is more important, it is difficult to find their own problems, others may see it at a glance, this is called the observer is confused