Vue – the router source code parsing | 6 k word – 【 the 】
- Hello everyone, I am Guanghui 😎
- This is
vue-router
Source code analysis of the next chapter, that is the end - This article mainly introduces the following points
- This paper introduces the
vue-router
How is scrolling handled The view, the link
How are components implemented?- How do route changes trigger rerendering and so on
- This paper introduces the
- 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 help
star
✨
- 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 that
vue-router
It 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 one
scrollBehavior
Parameters,scrollBehavior
There 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 that
HTML5History
And at initializationHashHistory
insetupListener
Is called whensetupScroll
Function to initialize the rolling-related logic - And in the
popstate
orhashchange
Called when the event triggers a route jumphandleScroll
Handling 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 the
src/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 utilization
History API
To complete the saving of the location- Developer.mozilla.org/zh-CN/docs/…
- in
popstate
Record the scrolling position and update the statusobj
thekey
- this
key
Is used instate
Is used to identify each route - May have a look
key
The 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 currentstate
And generates a unique for itkey
- through
replaceState
Will be addedkey
thestate
Saved to the current routeabsolutePath
on - Then listen to
popstate
Event, 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 changes
popstate
Save 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 of
positionStore
Object matches unique_key
To access the location - in
handleScroll
When you can pass_key
Retrieve the previously saved position
handleScroll
- The code that handles scrolling is located
src/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
$nextTick
In the callgetScrollPosition
Gets the previously saved location - Call the one that we passed in
scrollBehavior
Look at its return value to determine if scrolling is required - It also determines whether a wave is rolling asynchronously
- If so, wait for it
resolved
Call againscrollToPosition
- Otherwise call
scrollToPosition
getScrollPosition
,scrollToPosition
The 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
_key
frompositionStore
To read the saved location information scrollToPosition
The logic is clear, and it handles scrolling to the specifieddom
And scroll directly to a specific location
summary
vue-router
Handling scrolling is mainly utilizedHistory API
Feature 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
$nextTick
In the real deal with scrolling - It supports scenarios such as scrolling to a specified location, specified DOM, and asynchronous scrolling
The view components
vue-router
The built-inrouter-view
,router-link
Two 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 first
router-view
component
router-view
router-view
The primary responsibility of the routing component is to render it- Defined in the
src/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 receives
name
Let’s make a named view - Let’s focus on that
render
methods - 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 own
createElement
Method that uses the parent node insteadcreateElement
methods - Cannot pass because there is no this context
this.$route
To get the current route object, simply use the parent node’s$route
- In order to support parsing named slots, it does not use its own
- You can see that a marker has been added
routerView
Used mainly invue-devtools
Identified inview
Component and when looking for depth - A cache object is then declared
_routerViewCache
And assigned to itcache
Variable used in thekeep-alive
Quickly fetch cached routing components on activation - Start looking up from the current node
Vue root instance
And calculate it during the searchview
The depth of the component and whether it iskepp-alive
Wrapped and ininative
state depth
Mainly used to get the currentview
Indicates the route record- As I said,
vue-router
Is to support nested routines by, correspondingview
It 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, passunshift
intoroute.matched
Array, so the parent record must come first, the child record must come second, and the current exact match must come last- see
SRC/util/route. Js formatMatch method
- see
depth
The calculation in the encounterThe parent view
Component, increment by 1, increment by searching up continuouslydepth
Until I findVue root instance
Didn’t stop- When to stop
route.matched[depth]
The value is the currentview
Indicates 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
- As I said,
- We see first
The inactive
How does state render routing component instances- through
route.matched[depth]
Take out the currentview
Matched 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 reset
cache[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 called
fillPropsinData
fillprops
- If dynamic routing parameters are configured, the routing parameters are cached on the routing component instance and called
- call
h
Render the corresponding routing component instance
- through
- When a component is in
inactive
State, we can go fromcache
Fetch 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 noticed
data.registerRouteInstance
Method, yes, which is used to bind routing component instances to routing records
registerInstance
- Let’s take a look at the downsizing first
- Mainly in the
src/install.js
The 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 globally
beforeCreate
,destroyed
Hooks 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 located
src/install.js
In 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 one
Vm instances
andcallVal
As the reference - Then take the
vm
As the initial value of I - And then step by step
I assigned
And judgei
Whether the definition - In the end,
i
The value ofvm.$options._parentVnode.data.registerRouteInstance
- We then pass in two incoming parameters
i
In the call - Note that I is the method on the vm parent, not the method on the VM
- Let’s do a global search
registerRouteInstance
The keyword is found to be defined only inview.js
Student: Middle, which is equal torouter-view
In the component- Combine that with the previous one, I is
registerRouteInstance
isThe vm parent node
On, and onlyrouter-view
The component definesregisterRouteInstance
- So, only when
vm
isrouter-view
Is the child node ofregisterRouteInstance
Method is called i(vm, callVal)
Can be expressed asvm._parentVnode.registerRouteInstance(vm,vm)
- Combine that with the previous one, I is
- Look at the
registerRouteInstance
The 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
matched
Records of the routes currently matched are saved.name
Is the named view name- if
val
Exists, and the current route component is different from the incoming one, reassign - if
val
Does not exist, and the current route component is the same as the one passed in, also reassigns, but val isundefined
Is 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 in
view
componentrender
When, 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-alive
When the component is activated, the routing component needs to be bound to the routing record
summary
router-view
Is a functional component that sometimes needs to leverage the parent’s capabilities, such as parsing named slots using the parent’s rendering function- through
routerView
To identify theview
Components, convenientvue-devtools
To identify theview
Components and determinationsview
The depth of the component - Determine the current by looking up
view
The depth of thedepth
Through thedepth
The corresponding route record is obtained - Take it out again through
registerInstance
Bound routing component instance - If dynamic route parameters exist, fill them first
props
And then render it - if
view
bekeep-alive
Wrapped and ininactive
State, 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 called
updateRoute
Method, re – global_routerRoot._route
namely$route
The 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
- in
view
Component, will be used$parent.$route
The 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 the
install.js
The global mix, will_route
Defined as reactive, dependent_route
In the place of_route
When 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,
view
Rely on$route
, navigation resolution updated successfully$route
To triggerview
Apply colours to a drawing - Finished watching
view
Component, let’s look at another componentrouter-link
Link component
router-link
Components are defined insrc/components/link.js
In 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 clicked
to
Corresponding routing function - Because support click need to identify the style class, accurate matching
exact
Scene, so throughsameRoute
,isIncludedRoute
To achieve style class identification and accurate matching identification - When clicking, part of the special scene is shielded, such as clicking at the same time
ctrl
,alt
,shift
Etc.control keys
When, do not jump - After we look at the components, let’s look at them again
router
And provide us with instance methods
Instance properties, methods
router
A 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 to
this.$route
- The current route object is equal to
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 done
vue-router@2
All 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 ✨
- personal
github
, 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 aliases
vue-cli-plugin
www.npmjs.com/package/vue…
NPM package
- vue-cli-plugin-auto-alias
- @bryanadamss/drawing-board
- @bryanadamss/num2chn
- ant-color-converter