Vue – the router source code parsing words | | 1.3 w multiple warning – [on]
- Hello everyone, I am Guanghui 😎
vue-router
Is eachVue developers
A plugin that is accessible to all- With the idea of going back to the source, I started
vue-router
Path to source code analysis 😂 - I worked overtime every day, so it took months
- Time to drag a very long, so there may be incoherent thinking of the situation, but also hope forgive 🤣
- Due to the nuggets word limit, so divided into the next three to introduce
- The first part of this paper mainly introduces the design ideas and basic principles of front-end routing.
vue-router
Related terms, directory structure, installation, instantiation, initialization related implementation - In the middle part, the mechanism of routing matching and navigation guard triggering are introduced
- The next chapter is the final chapter, introducing some non-core features such as scroll processing,
view,link
Component implementation - 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 】
Design ideas
- In parsing
vue-router
So before we do this routing library, we need to know what is routing, what is front-end routing? What is the general approach to implementing front-end routing?
What is front-end routing
- For the definition of routing, the wiki defines it like this;
Routing is the activity of transferring information from a source address to a destination address over an interconnected network. Routing occurs at the third layer in the OSI network reference model, the network layer. Routing guides packet forwarding through a number of intermediate nodes to their final destination. The hardware is called a router. Routing typically guides packet forwarding according to a routing table -- a table stored with the best paths to each destination
- The above definition may be official, but we can extract some important points
- Routing is an activity that transfers information from a source address to a destination address.
- To complete such an activity you need something very important – a routing table – a mapping of source and destination addresses
In web backend development, "route" refers to the corresponding handler assigned according to the URL.
; Citing thehttps://www.zhihu.com/question/46767015
@ royal ShiJun- The user enters a URL, the browser passes it to the server, the server matches the mapping table, finds the corresponding handler, and returns the corresponding resource (page or other);
- For the front end, the routing concept came along with it
spa
The emergence of; inspa
Before the emergence, the page jump (navigation) is controlled by the server, and there is an obvious white screen jump process;spa
After the emergence, for a better experience, no longer let the server control the jump, so the front end routing emerged, the front end can freely control the rendering of components, to simulate the page jump - summary
- Server routing basis
url
Assign corresponding handler, return page, interface return - Front-end routing is through
js
According to theurl
Returns the corresponding component
- Server routing basis
How to implement front-end routing
- Looking at the above definition, to implement routing, you need one very important thing – a routing mapping table;
- When the server switches to the routing page, the mapping table reflects the following information
url
andpage
The relationship between - Now the front end is basically modular, so the front end route mapping table reflects
url
andcomponent
The relationship between
- When the server switches to the routing page, the mapping table reflects the following information
- Like the pseudocode below
// Use es6 map
const routeMap=new Map([['/', home page component], ['/bar',Bar component], ['/bar/foo', component Foo]])// You can also use object literals
const routeMap={
'/': Home page component,'/bar': the Bar component,'/bar/foo'Components: Foo}Copy the code
- So with the mapping table, we know that
url
Mapping relationships with components; - However, the mapping maintains only a relationship, it doesn’t help us complete the access
/bar
To return toThe Bar component
Such a process, so we also need a matcher, to help us complete fromurl
tocomponent
Matching work; - Is there one?
Route mapping table
andmatcher
You can implement front-end routing? - our
spa
It runs in the browser environment, and the browser doesForward, return
Functional, we need to record the accessurl
;- We know that to achieve this kind of similarity
Undo, restore
A data structure must be used for the function ofThe stack (stack)
; Each visiturl
That will beurl
.push
To the stack, return, executepop
Can get the last visiturl
- Fortunately, the browser platform already provides such a stack, so we don’t need to implement it ourselves. We just need to call its window.history implementation
- We know that to achieve this kind of similarity
- I drew a picture of the collaboration between the three
- sequence.png
- sequence.png
- When we access a URL, for example
/foo
The dispenser will take it/foo
The corresponding component is routed through the mapping table and returned to render, pushing the access record onto the history stack - When we access something by going forward/backward
url
Is first found in the history stackurl
And then the dispenser takes iturl
Go to the component and return to render; However, this is accessed forward/backward, so there is no need to push the history stack
conclusion
- To implement a front-end route, there are three parts
- Route mapping table
- One that can express
url
andcomponent
Relational mapping table that can be usedMap
,Object literals
To implement the
- One that can express
- matcher
- Responsible for visiting
url
, to find out the correspondingcomponent
- Responsible for visiting
- History stack
- Browser platform, already native support, no implementation, direct call interface
- Route mapping table
- You don’t have to worry about their implementation, you just need to know that there are three things and how they work together;
- We’ll look at that later
vue-router
How to use them to achieve front-end routing
The term
- Based on the analysis of
vue-router
Before source code, we first understandvue-router
Some of the concepts often appear in terms, if it is difficult to understand, you can first skip, behind encounter, and then come back to see;
Routing rules, configuration objects (RouteConfig
)
- Route configuration item, used to describe the route
- The red box in the following figure shows the routing configuration objects
- route-config.png
- because
vue-router
Is to supportEmbedded routines by
So configuration objects can also be nested with each other - The complete shape is shown below
interface RouteConfig = {
path: string, component? : Component,// Routing componentname? :string.// Name the routecomponents? : { [name:string]: Component }, // Name the view componentredirect? :string | Location | Function, props? :boolean | Object | Function, alias? :string | Array<string>, children? :Array<RouteConfig>, // Nested bybeforeEnter? :(to: Route, from: Route, next: Function) = > void, meta? :any./ / server +caseSensitive? :boolean.// Is the matching rule case-sensitive? (Default: false)pathToRegexpOptions? :Object // Compile the re option
}
Copy the code
Routing record (RouteRecord
)
- Each routing rule generates a routing record. Nested and alias routes also generate a routing record. Is a component of the routing mapping table
const record: RouteRecord = {
path: normalizedPath,
regex: compileRouteRegex(normalizedPath, pathToRegexpOptions), // Use the path-to-regexp package to generate enhanced regex objects to match path, which can be used to match dynamic routes
components: route.components || { default: route.component }, / / save the routing module, support named view named view at https://router.vuejs.org/zh/guide/essentials/named-views.html#
instances: {}, // Save the routing components that need to be rendered for each named router-view
name,
parent,
matchAs,
redirect: route.redirect, // Redirect the route configuration object
beforeEnter: route.beforeEnter, // Route exclusive guard
meta: route.meta || {}, / / meta information
props: // Dynamic route parameter transfer; https://router.vuejs.org/zh/guide/essentials/passing-props.html# routing components involved
route.props == null
? {}
: route.components // The parameter passing rule for the named view needs to use the rule specified by route.props
? route.props
: { default: route.props }
}
Copy the code
Routing object (Route
)
Route
Indicates the status of the active route, including the current routeURL
Parsing the information, andURL
Matched route recordsthe (route records
).https://router.vuejs.org/zh/api/# routing object
- Notice, this tells you one thing
Route
You can associate more than oneRouteRecord
- through
this.$route
That’s what I’m accessingRoute
object - route.png
- The route object is immutable (
immutable
A new object is generated after each successful navigation
Location (Location
)
- It is not
window.location
The reference,vue-router
One is defined internallyLocation
, is an object that describes the location of a target; $router.push/replace
,The router - to link
Receiving isLocation object
https://router.vuejs.org/zh/api/#to
vue-router
The interior can be aurl string
Converted toLocation object
So to be exact$router.push/replace
,The router - to link
It’s always the sameRawLocation object
RawLocation object
isString
andLocation
The union type of
export type RawLocation = string | Location
export interfaceLocation { name? :stringpath? :stringhash? :stringquery? : Dictionary<string | (string | null|) []null | undefined> params? : Dictionary<string> append? :booleanreplace? :boolean
}
Copy the code
Routing component (RouteComponent
)
- When a route is matched successfully, the route must be stored in the
router-view
Render a component that needs to be renderedRouting component
RouteConfig
In theComponent, the components,
Defined in theVue components
That’s the routing component- Specificity of routing components
- haveThis parameter is valid only for routing componentsThe guard (
BeforeRouteEnter, beforeRouteUpdate, beforeRouteLeave
) - Have you ever called from a component, as I have
beforeRouteEnter
The discovery didn’t work because the guard could only work inRouting component
Is called in all non-routed components, including descendants of routed components; If you want to implement it in a routing componentbeforeRouteEnter
Similar to guard listening effect, can passwatch $route
To manually determine
- haveThis parameter is valid only for routing componentsThe guard (
- The red box is
Routing component
- route-component.png
- After reading the above terms, you may still be in the clouds, nothing, will be explained in detail, now you just need to have a general understanding;
The environment
vue-router
Version:v3.1.6
node
Version:v8.17.0
- Address of analysis warehouse:
https://github.com/BryanAdamss/vue-router-for-analysis
- To highlight
- Pay attention to see
The commit record
.commit
Recorded the whole process of my analysis - If you feel ok, don’t forget
star
,fork
🤞
- Pay attention to see
- To highlight
The directory structure
- First we are going to
vue-router
warehouseclone
Let’s see what the directory structure looks like git clone [email protected]:BryanAdamss/vue-router-for-analysis.git
directory
- You can see the following directories
- directory.png
- Despite the catalogue, I’ve already highlighted it for you
- We really only need to focus on the following directories or files
examples
- It contains a carefully prepared official case
- Not only tell you
vue-router
It also tells you how to deal with complex scenarios such as permission control and dynamic routing. Anyway, it’s worth checking out; - examples.png
- In addition, we can use these examples for debugging when analyzing source code
- Add source code where you want to debug
Debugger Indicates a breakpoint
And then launch the examplenpm run dev
Open,localhost:8080
Can be
- Add source code where you want to debug
src
directory- This is a
vue-router
Where the source code is stored is where we need to focus our attention - src.png
- Pick up some important catalogs first
components
The directory is for storing built-in componentsrouter-link
,router-view
thehistory
Is the storage coreThe history class
The place whereutil
Contains some helper functionsindex.js
isvue-router
Entry file, alsovue-router
Where the class is definedinstall.js
Is the file where the installation logic resides
- This is a
flow/declarations.js
- It is a
vue-router
The flow type declaration file, from which we can knowvue-router
What do core classes (objects) look like in - Inside it, it looks something like this
- declarations.png
- It is a
Based on example
- Let’s start with the most basic example
import Vue from 'vue'
import VueRouter from 'vue-router'
// 1. Use plugin.
// Install the plug-in
Vue.use(VueRouter)
// 2. Define route components
// Define the routing component
const Home = { template: '<div>home</div>' }
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
// 3. Create the router
// Instantiate vue-router
const router = new VueRouter({
mode: 'history'.routes: [{path: '/'.component: Home },
{ path: '/foo'.component: Foo },
{ path: '/bar'.component: Bar },
]
})
// 4. Create and mount root instance.
new Vue({
router, // Inject the Router instance
template: `
Basic
-
/
-
/foo
-
/bar
`
}).$mount('#app')
Copy the code
- As you can see, first use
vue
The plugin syntax is installedvue-router
; And then we instantiateVueRouter
; And finally we are going toVueRouter
Is injected into theVue
- This really involves three core processes: installation, instantiation, and initialization
- Let’s start with these three processes
The installation process
- We know, as long as it’s
vue plugin
There must be oneinstall
Methods;- Cn.vuejs.org/v2/guide/pl…
- As mentioned above
vue-router
The entry file is insrc/index.js
Yes, let’s goindex.js
Look in theinstall
methods
// src/index.js
/* @flow */
import { install } from './install' // Import the installation method./ / VueRouter class
export default class VueRouter {... }... VueRouter.install = install// The install method is automatically called when vue. use is mounted
VueRouter.version = '__VERSION__'
// Browser environment, automatically install VueRouter
if (inBrowser && window.Vue) {
window.Vue.use(VueRouter)
}
Copy the code
- As you can see, it’s imported at the beginning
install
Method and mount it directly as a static methodVueRouter
Go on, like this, inVue.use(VueRouter)
When,install
The method will be called; - You can see if in the browser environment, and through
The script tag
Introduction of the form ofVue
(inwindow
On the mountVue
Global variables) will try to be used automaticallyVueRouter
- Let’s see
install.js
What is the
install.js
install
It’s not that long
import View from './components/view'
import Link from './components/link'
export let _Vue // To avoid wrapping Vue as a dependency
/ / install method
export function install (Vue) {
if (install.installed && _Vue === Vue) return // Avoid repeated installation
install.installed = true
_Vue = Vue // Preserve Vue references
const isDef = v= >v ! = =undefined
// Associate the routing component with the router-view component
const registerInstance = (vm, callVal) = > {
let i = vm.$options._parentVnode
/ / call the vm $options. _parentVnode. Data. RegisterRouteInstance method
// This method only exists in the router-view component, which is defined in (.. / components/view. Js @ 71 lines)
// 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)
}
}
// Register global mixin
Vue.mixin({
beforeCreate () {
// this === new Vue({router:router}) === Vue root instance
// Check whether vue-router is used
if (isDef(this.$options.router)) {
// Save some information on the Vue root instance
this._routerRoot = this // Save the Vue instance where VueRouter is mounted, which is the root instance
this._router = this.$options.router // Save the VueRouter instance. This.$options.router only exists on the Vue root instance
// beforeCreate is called when the beforeCreate hook is triggered
this._router.init(this) // Initialize the VueRouter instance and pass in the Vue root instance
// 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)
} else {
// Backtrack to _routerRoot
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
}
// 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)}})// Add $router and $route attributes to the prototype
Object.defineProperty(Vue.prototype, '$router', {
get () { return this._routerRoot._router }
})
Object.defineProperty(Vue.prototype, '$route', {
// Each component accessing $route ends up accessing _route of the Vue root instance
get () { return this._routerRoot._route }
})
// Register router-view and router-link global components
Vue.component('RouterView', View)
Vue.component('RouterLink', Link)
const strats = Vue.config.optionMergeStrategies
// use the same hook merging strategy for route hooks
strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
}
Copy the code
- As you can see,
install
Method does the following things
Avoid repeated installation
- By adding
installed
Flag to determine whether repeated installation is possible
keepVue
Reference, avoid willVue
Packaged as a dependency
install
When the method is called, theVue
Passed in as a parameter,Vue
It’s going to be assigned to a predefined value_Vue
variable- In other modules, you can import this
_Vue
, so that you can accessVue
, and avoid willVue
Packaged as a dependency - This is a useful tip for plug-in development
A global mixin was registered
- This mixin will affect every one created after registration
Vue
Instance, which is each of the followingVue
Instances execute code that is mixed in - Let’s take a look at the code that’s been mixed in
// Register global mixin
Vue.mixin({
beforeCreate () {
// this === new Vue({router:router}) === Vue root instance
// Check whether vue-router is used
if (isDef(this.$options.router)) {
// Save some information on the Vue root instance
this._routerRoot = this // Save the Vue instance where VueRouter is mounted, which is the root instance
this._router = this.$options.router // Save the VueRouter instance. This.$options.router only exists on the Vue root instance
// beforeCreate is called when the beforeCreate hook is triggered
this._router.init(this) // Initialize the VueRouter instance and pass in the Vue root instance
// 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)
} else {
// Backtrack to _routerRoot
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
}
// 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
- It registers two lifecycle hooks
beforeCreate
anddestroyed
; - Pay attention to
- Of the two hooks,
this
Refers to the hook that was being calledVue instance
; - The logic in these hooks is not executed during the installation process and is invoked only when the hook is executed during component instantiation
- Of the two hooks,
- We see first
beforeCreate
hook - It judged first
this.$options.router
Does it exist? We arenew Vue({router})
When,router
It has been saved toVue root instance
the$options
On, and the othersVue instance
the$options
There was norouter
the- so
if
The statement inthis === new Vue({router})
, will be executed becauseVue root instance
There is only one, so this logic will only be executed once - We can do it at
if
In the printthis
Look at it with a debugging tool - root-instance.png
- Indeed,
if
The logic is executed only once, andthis
Is the point toVue root instance
- so
- We look at the
if
What exactly did you do- Saved on the root instance
_routerRoot
, used to identifyrouter
The mountVue instance
- Saved on the root instance
VueRouter
Instance (router
) - right
router
Initialized (init
)router
The initialization logic will be examined later
- On the root instance, responsiveness is defined
_route
attribute- ensure
_route
Changes,router-view
It will be re-rendered, which we’ll see laterrouter-view
More on this in the component section
- ensure
- Saved on the root instance
- Let’s see
else
What exactly did- This is primarily defined for each component
_routerRoot
, is the use of layer by layer backtracking search
- This is primarily defined for each component
- We see another one
registerInstance
Method, it’s inbeforeCreate
,destroyed
They all get called, just with different numbers of arguments- in
beforeCreate
Two arguments are passed to thethis
The currentVue instance
, and indestroyed
Only one is passed inVue instance
- We are talking about
router-view
We’ll talk about it in detail, but you just need to know that it’s used forrouter-view
Component association or unbindingRouting component
Can be used - Pass two parameters to associate, pass one parameter to unbind
- in
Add instance properties, methods
- in
Vue
Injection on the prototype$router, $route
Attribute, convenient inVue instance
Through thethis.$router
,this.$route
Quick access to
Register router-view and router-link global components
- through
Vue.component
Syntax registeredrouter-view
androuter-link
Two global components
Set the merge policy for routing component guards
- Of the routing component
beforeRouteEnter
,beforeRouteLeave
,beforeRouteUpdate
Guard merge strategy
conclusion
- Let’s summarize the installation process with a diagram
- install.png
Instantiation process
- After looking at the installation process, let’s move on
VueRouter
The instantiation process of - In this video, we’re going to focus on the instantiation process, so we’re just going to look at
constructor
The core logic in
Constructor of VueRouter
- We open
src/index.js
Look at theVueRouter
The constructor
// src/index.js
export default class VueRouter {
constructor (options: RouterOptions = {}) {
this.app = null // Save the mount instance
this.apps = [] // VueRouter supports multiple instances
this.options = options
this.beforeHooks = [] // Receive beforeEach hook
this.resolveHooks = [] // Accept beforeResolve hook
this.afterHooks = [] // Receive afterEach hook
this.matcher = createMatcher(options.routes || [], this) // Create a routing matcher object and pass in the Routes routing configuration list and VueRouter instance
let mode = options.mode || 'hash'
this.fallback = mode === 'history'&&! supportsPushState && options.fallback ! = =false // Whether to roll back
if (this.fallback) {
mode = 'hash'
}
// In a non-browser environment, enforce abstract mode
if(! inBrowser) { mode ='abstract'
}
this.mode = mode
// Instantiate different history instances according to different modes
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
- Constructors do a couple of things
Receive RouterOptions
- As you can see, the constructor accepts one
Options Object
And its type isRouterOptions
Let’s seeRouterOptions
- Open the
flow/declarations.js
// flow/declarations.jsdeclare type RouterOptions = { routes? :Array<RouteConfig>;// List of route configuration rulesmode? : string;// Routing modefallback? : boolean;// Whether to enable rollbackbase? : string;/ / the base addresslinkActiveClass? : string;// Router-link activated class namelinkExactActiveClass? : string;// Router-link specifies the class name for precise activationparseQuery? :(query: string) = > Object; // Define the method of parsing QSstringifyQuery? :(query: Object) = > string; // Customize the method to serialize QSscrollBehavior? :(// Control the scrolling behavior
to: Route,
from: Route, savedPosition: ? Position) = > PositionResult | Promise<PositionResult>;
}
Copy the code
RouterOptions
Defines theVueRouter
All options that can be received;- Router.vuejs.org/zh/api/#rou…
- Let’s focus on the following option values
routes
Is the route configuration rule list, which is used to generate the route mapping table later.- It is an array, and each entry is a routing configuration rule (
RouteConfig
),RouteConfig
Refer back to the terminology section;
- It is an array, and each entry is a routing configuration rule (
mode
,fallback
It’s related to the routing mode- More on that later
VueRouter
Routing mode of
- More on that later
Attribute assigns an initial value
- Some attributes are assigned initial values, for example, to receive
Global navigation Guard (beforeEach, beforeResolve, afterEach)
The array is initialized
Create the matcher
- through
createMatcher
To generate thematcher
- this
The matcher object
It’s the matcher we were talking about originally, which is responsible for URL matching, and it receives itroutes
andThe router instances
;createMatcher
It’s not just createdmatcher
, also createdThe routing mapping table RouteMap
We’ll take a closer look later
Determine the routing mode
- The three routing modes will be discussed later
- Now we just need to know
VueRouter
How is the routing mode determined VueRouter
Depending on theoptions.mode
,options.fallback
,supportsPushState
,inBrowser
To determine the final routing mode- Make sure the
fallback
.fallback
Only if the user has set itmode:history
The current environment does not support itpushState
And the user proactively declares that the rollback is requiredfallback
Just fortrue
- when
Fallback to true
When usinghash
Mode; - If it turns out to be in a non-browser environment, it is forced
abstract
model - route-mode.png
inBrowser
andsupportsPushState
The implementation is simple
// src/util/dom.js
export const inBrowser = typeof window! = ='undefined' // Check whether window exists directly to determine whether it is in the browser environment
// src/util/push-state.js
export const supportsPushState =
inBrowser && // Browser environment
(function () {
const ua = window.navigator.userAgent
if (
(ua.indexOf('Android 2.')! = = -1 || ua.indexOf('the Android 4.0')! = = -1) &&
ua.indexOf('Mobile Safari')! = = -1 &&
ua.indexOf('Chrome') = = = -1 &&
ua.indexOf('Windows Phone') = = = -1
) {
return false // Special browsers do not support it directly
}
return window.history && typeof window.history.pushState === 'function' // Determine whether pushState is supported}) ()Copy the code
Generate different History instances based on routing patterns
- Generate different routing patterns based on the previous step
The History instance
, about the routing mode,The History instance
Back to tell
summary
VueRouter
The constructor of the- To receive a
RouterOptions
- We then assign initial values to some attributes
- To generate the
matcher
matcher - Determine the routing mode
- Different routes are generated according to different routing modes
The History instance
- To receive a
Create matcher
- Let’s take a closer look
createMatcher
Implementation inside createMatcher
The implementation of thesrc/create-matcher.js
In the
// src/create-matcher.js./ / the Matcher type
export type Matcher = {
match: (raw: RawLocation, current? : Route, redirectedFrom? : Location) = > Route; // Match method
addRoutes: (routes: Array<RouteConfig>) = > void; // Add the Route method
};
// Matcher factory function
export function createMatcher (
routes: Array<RouteConfig>, // Route configuration list
router: VueRouter / / VueRouter instance
) :Matcher {
const { pathList, pathMap, nameMap } = createRouteMap(routes) // Create a routing mapping table
// Add a route
function addRoutes (routes) {
// Since pathList, pathMap, nameMap were passed in, createRouteMap will perform the add logic
createRouteMap(routes, pathList, pathMap, nameMap)
}
// Pass location to return a matching Route object
function match (raw: RawLocation, currentRoute? : Route, redirectedFrom? : Location) :Route {... }// Return the Matcher object, exposing the match, addRoutes methods
return {
match,
addRoutes
}
}
Copy the code
- As you can see,
createMatcher
Method receives a list of routing configuration rules andThe router instances
, returns aMatcher
object Matcher
The object contains one for matchingmatch
Method and a dynamic route adding methodaddRoutes
Methods;- And both methods are declared in
createMatcher
Internally, because of the closure nature, it can accesscreateMatcher
All variables of the scope
- And both methods are declared in
summary
- So let’s conclude
createMatcher
The logic of the - create-matcher.png
- We can see that
createMatcher
andaddRoutes
MethodcreateRouteMap
Method, they just pass different parameters, from the name of the method, this method must andRouting table RouteMap
The relevant - Let’s see
createRouteMap
The implementation of the
createRouteMap
createRouteMap
Methods insrc/create-route-map.js
In the
// Create a route mapping map and add a route record
export function createRouteMap (
routes: Array<RouteConfig>, // Route configuration listoldPathList? :Array<string>, / / the old pathListoldPathMap? : Dictionary<RouteRecord>,/ / the old pathMapoldNameMap? : Dictionary<RouteRecord>/ / the old nameMap
) :{
pathList: Array<string>,
pathMap: Dictionary<RouteRecord>,
nameMap: Dictionary<RouteRecord>
} {
// If the old routing-related mapping list and map exist, use the old initialization (to enable adding routes)
// the path list is used to control path matching priority
const pathList: Array<string> = oldPathList || []
// $flow-disable-line
const pathMap: Dictionary<RouteRecord> = oldPathMap || Object.create(null)
// $flow-disable-line
const nameMap: Dictionary<RouteRecord> = oldNameMap || Object.create(null)
// Traverses the route configuration object to generate/add route records
routes.forEach(route= > {
addRouteRecord(pathList, pathMap, nameMap, route)
})
// ensure wildcard routes are always at the end
// Make sure path:* is always at the end
for (let i = 0, l = pathList.length; i < l; i++) {
if (pathList[i] === The '*') {
pathList.push(pathList.splice(i, 1) [0])
l--
i--
}
}
// Development environment, indicating that non-nested path must start with/or *
if (process.env.NODE_ENV === 'development') {
// warn if routes do not include leading slashes
const found = pathList
// check for missing leading slash
.filter(path= > path && path.charAt(0)! = =The '*' && path.charAt(0)! = ='/')
if (found.length > 0) {
const pathNames = found.map(path= > ` -${path}`).join('\n')
warn(false.`Non-nested routes must include a leading slash character. Fix the following routes: \n${pathNames}`)}}return {
pathList,
pathMap,
nameMap
}
}
Copy the code
- You can see
createRouteMap
Returns an object that containspathList
,pathMap
andnameMap
pathList
Stored in theroutes
All of thepath
pathMap
Maintenance ispath
andRoute records RouteRecord
The mapping ofnameMap
Maintenance isname
andRoute records RouteRecord
The mapping of- because
VueRouter
Support named Routing
- because
- In the last two, it’s all about finding a quick match
Routing record
- You can take a look and use the following
routes
callcreateRouteMap
What will be returned
[{path: '/'.component: Home },
{ path: '/foo'.component: Foo ,
children:[
{
path:'child'.component:FooChild
}
]},
{ path: '/bar/:dynamic'.component: Bar },
]
Copy the code
- route-map-obj.png
- There is no named route, so
nameMap
Is empty pathList
Store all of thempath
One of them is empty/
In thenormalizePath
Was deletedpathMap
Recorded every single one of thempath
andCorresponding RouteRecord
Mapping relation of
summary
VueRouter
The routing mapping table is composed of three parts:pathList
,pathMap
,nameMap
; The latter two are for quick lookupscreateRouteMap
The logic of the- Check whether the routing mapping table already exists. If yes, use the mapping table. Otherwise, create a mapping table.
- And that’s done
createRouteMap
Dual functionality created/added
- And that’s done
- Then traverse
routes
, each in turnroute
calladdRouteRecord
To generate aRouteRecord
And update thepathList
,pathMap
andnameMap
- Due to the
pathList
In the future the logic will be used to traverse the match, for performance, so will be requiredpath:*
Placed inpathList
At the end of the - Finally check the non-nested routines
path
Whether to/
or*
At the beginning
- Check whether the routing mapping table already exists. If yes, use the mapping table. Otherwise, create a mapping table.
- It is summarized as follows
- create-route-map-sequence.png
- Next, how is the routing record generated
addRouteRecord
- This method mainly creates the routing record and updates the routing mapping table
- Located in the
src/create-route-map.js
// src/create-route-map.js
// Add route records and update pathList, pathMap, and nameMap
function addRouteRecord (
pathList: Array<string>, pathMap: Dictionary<RouteRecord>, nameMap: Dictionary<RouteRecord>, route: RouteConfig, parent? : RouteRecord,// Record the parent routematchAs? :string // Used when dealing with alias routes
) {
const { path, name } = route
if(process.env.NODE_ENV ! = ='production') {
// Route. path cannot be emptyassert(path ! =null.`"path" is required in a route configuration.`)
// route.component cannot be string
assert(
typeofroute.component ! = ='string'.`route config "component" for path: The ${String(
path || name
)} cannot be a ` + `string id. Use an actual component instead.`)}const pathToRegexpOptions: PathToRegexpOptions =
route.pathToRegexpOptions || {}
// Generate a formatted path(the child route will concatenate the path of the parent route)
const normalizedPath = normalizePath(path, parent, pathToRegexpOptions.strict)
// Is the matching rule case-sensitive? (Default: false)
if (typeof route.caseSensitive === 'boolean') {
pathToRegexpOptions.sensitive = route.caseSensitive
}
// Generates a routing record
const record: RouteRecord = {
path: normalizedPath,
regex: compileRouteRegex(normalizedPath, pathToRegexpOptions), // Use the path-to-regexp package to generate enhanced regex objects to match path, which can be used to match dynamic routes
components: route.components || { default: route.component }, / / save the routing module, support named view named view at https://router.vuejs.org/zh/guide/essentials/named-views.html#
instances: {}, // Save the router-view instance
name,
parent,
matchAs,
redirect: route.redirect, // Redirect the route configuration object
beforeEnter: route.beforeEnter, // Route exclusive guard
meta: route.meta || {}, / / meta information
props: // Dynamic route parameter transfer; https://router.vuejs.org/zh/guide/essentials/passing-props.html# routing components involved
route.props == null
? {}
: route.components // The parameter passing rule for the named view needs to use the rule specified by route.props
? route.props
: { default: route.props }
}
// Handle child routing
if (route.children) {
// Warn if route is named, does not redirect and has a default child route.
// If users navigate to this route by name, the default child will
// not be rendered (GH Issue #629)
// https://github.com/vuejs/vue-router/issues/629
// Named route && Does not use redirection && Child routing when path is '' or /, child routing will not be rendered when jumping with the name of the parent route
if(process.env.NODE_ENV ! = ='production') {
if( route.name && ! route.redirect && route.children.some(child= > ^ / / /? $/.test(child.path))
) {
warn(
false.`Named Route '${route.name}' has a default child route. ` +
`When navigating to this named route (:to="{name: '${ route.name }'"), ` +
`the default child route will not be rendered. Remove the name from ` +
`this route and use the name of the default child route for named ` +
`links instead.`)}}// Iterate over the generated child routing record
route.children.forEach(child= > {
const childMatchAs = matchAs // matchAs If it has the value, it indicates that the current route is an alias route. The child routes of the alias route need to be generated separately. The path prefix must be matchAs
? cleanPath(`${matchAs}/${child.path}`)
: undefined
addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs)
})
}
// If the current path does not exist in pathMap, update pathList and pathMap
if(! pathMap[record.path]) { pathList.push(record.path) pathMap[record.path] = record }// Handle aliases; https://router.vuejs.org/zh/guide/essentials/redirect-and-alias.html#%E5%88%AB%E5%90%8D
if(route.alias ! = =undefined) {
const aliases = Array.isArray(route.alias) ? route.alias : [route.alias] // alias supports string, and Array< string >
for (let i = 0; i < aliases.length; ++i) {
const alias = aliases[i]
if(process.env.NODE_ENV ! = ='production' && alias === path) { // The value of alias is the same as that of 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
}
// Generate an alias routing configuration object
const aliasRoute = {
path: alias,
children: route.children
}
// Add an alias route record
addRouteRecord(
pathList,
pathMap,
nameMap,
aliasRoute, // Alias route
parent, // The parent route of the current route has the same parent route because the current route is named separately
record.path || '/' // matchAs, used to generate child routes for alias routes;
)
/ /! Summary: After the alias is set for the current route, the route records are generated for the current route and all its children. The path prefix of the children is matchAs(that is, the path of the alias route).}}// Handle named routes
if (name) {
/ / update the nameMap
if(! nameMap[name]) { nameMap[name] = record }else if(process.env.NODE_ENV ! = ='production' && !matchAs) {
// Route duplicate warning
warn(
false.`Duplicate named routes definition: ` +
`{ name: "${name}", path: "${record.path}" }`)}}}Copy the code
- Look at the logic
- The routing rules are checked
path
andcomponent
- generate
path-to-regexp
The option topathToRegexpOptions
- formatting
path
If it is nested, the parent route will be appendedpath
- Generating Routing Records
- Processing nested routines, recursive generation of child routing records
- update
pathList
,pathMap
- Process alias routing and generate alias routing records
- Handle named routes, updates
nameMap
- The routing rules are checked
- Let’s look at some of the core logic
Generating Routing Records
- Route records record core information about routes
const record: RouteRecord = {
path: normalizedPath,// Normalized path
regex: compileRouteRegex(normalizedPath, pathToRegexpOptions), // Use the path-to-regexp package to generate enhanced regex objects to match path, which can be used to match dynamic routes
components: route.components || { default: route.component }, / / save the routing module, support named view named view at https://router.vuejs.org/zh/guide/essentials/named-views.html#
instances: {}, // Save the router-view instance
name,
parent,// Record the parent route
matchAs, // Alias routing is required
redirect: route.redirect, // Redirect the route configuration object
beforeEnter: route.beforeEnter, // Route exclusive guard
meta: route.meta || {}, / / meta information
props: // Dynamic route parameter transfer; https://router.vuejs.org/zh/guide/essentials/passing-props.html# routing components involved
route.props == null
? {}
: route.components // The parameter passing rule for the named view needs to use the rule specified by route.props
? route.props
: { default: route.props }
}
Copy the code
- The routing record has a
regex
Field, which is an enhanced regular expression, it is the key to achieve dynamic routing matching regex
Is through thecompileRouteRegex
Method, which is called insidepath-to-regexp
import Regexp from 'path-to-regexp'.// Use the path-to-regexp package to generate regex corresponding to route, which can be used to generate regular expressions for dynamic routes
function compileRouteRegex (
path: string,
pathToRegexpOptions: PathToRegexpOptions
) :RouteRegExp {
const regex = Regexp(path, [], pathToRegexpOptions)
if(process.env.NODE_ENV ! = ='production') {
const keys: any = Object.create(null)
regex.keys.forEach(key= > {
// Repeat key extractwarn( ! keys[key.name],`Duplicate param keys in route with path: "${path}"`
)
keys[key.name] = true})}return regex
}
Copy the code
- We look at the
path-to-regexp
How is it used - Website is: www.npmjs.com/package/pat…
- Regexp takes three arguments
Path, keys, options
;path
Is the path to be converted to regular,keys
Is used to receive inpath
Found in thekey
, can be passed in or directly used on the return valuekeys
Properties,options
For the options
const pathToRegexp= require('path-to-regexp')
const regexp = pathToRegexp("/foo/:bar");
// regexp = /^\/foo\/([^\/]+?) / /? $/i
// :bar is processed as a group of regees. When the re is executed, the value of bar can be retrieved from the group
console.log(regexp.keys) // keys = [{ name: 'bar', prefix: '/', suffix: '', pattern: '[^\\/#\\?]+?', modifier: '' }]
Copy the code
- Through the following example, we can know how to achieve the dynamic route to obtain parameter values
// test.js
const pathToRegexp= require('path-to-regexp')
const regexp = pathToRegexp("/foo/:bar");
console.log(regexp.keys)// Record key information
const m = '/foo/test'.match(regexp) // The regular group records value information
console.log('key:',regexp.keys[0].name,',value:',m[1])
Copy the code
- path-to-regex-demo.png
Generate nested routines by recording
- We know that
vue-router
Inline routing is supported. Let’s see how to generate nested routing records
// src/create-route-map
// Handle child routing
if (route.children) {
// Warn if route is named, does not redirect and has a default child route.
// If users navigate to this route by name, the default child will
// not be rendered (GH Issue #629)
// https://github.com/vuejs/vue-router/issues/629
// Named route && Does not use redirection && Child routing when path is '' or /, child routing will not be rendered when jumping with the name of the parent route
if(process.env.NODE_ENV ! = ='production') {
if( route.name && ! route.redirect && route.children.some(child= > ^ / / /? $/.test(child.path))
) {
warn(
false.`Named Route '${route.name}' has a default child route. ` +
`When navigating to this named route (:to="{name: '${ route.name }'"), ` +
`the default child route will not be rendered. Remove the name from ` +
`this route and use the name of the default child route for named ` +
`links instead.`)}}// Iterate over the generated child routing record
route.children.forEach(child= > {
const childMatchAs = matchAs // matchAs If it has the value, it indicates that the current route is an alias route. The child routes of the alias route need to be generated separately. The path prefix must be matchAs
? cleanPath(`${matchAs}/${child.path}`)
: undefined
addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs)
})
}
Copy the code
- First, in view of the
# 629
The question gives a warning# 629
The main problem is when a route isThe named route && does not use a redirection && subroute configuration object whose path is '' or /
When the parent route is usedname
When jumping, child routes will not be rendered
- It then iterates through the list of child routing rules to generate child routing records
- It also handles the child routing of alias routes
- If the parent route is marked as an alias route, the child route is
path
The parent route needs to be added in front of itpath
And then regenerate it into a record
- If the parent route is marked as an alias route, the child route is
- It also handles the child routing of alias routes
- We can look at the following nested pattern, what does the generated routing mapping table look like
[{path: '/parent'.component: Parent,
children: [{path: 'foo'.component: Foo },
]
}
]
Copy the code
- nested-route.png
- As you can see, subpaths
path
The parent route is appended before the sessionpath
Generate alias routing records
VueRouter
Aliases for routes are supported./a
The alias is/b
Means when the user accesses/b
When,URL
Will stay for/b
, but the route match is/a
, like user access/a
The same- How do peppers generate alias routing records
// src/create-route-map.js
if(route.alias ! = =undefined) {
const aliases = Array.isArray(route.alias) ? route.alias : [route.alias] // alias supports string, and Array< string >
for (let i = 0; i < aliases.length; ++i) {
const alias = aliases[i]
if(process.env.NODE_ENV ! = ='production' && alias === path) { // The value of alias is the same as that of 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
}
// Generate an alias routing configuration object
const aliasRoute = {
path: alias,
children: route.children
}
// Add an alias route record
addRouteRecord(
pathList,
pathMap,
nameMap,
aliasRoute, // Alias route
parent, // The parent route of the current route has the same parent route because the current route is named separately
record.path || '/' // matchAs, used to generate child routes for alias routes;
)
/ /! Summary: After the alias is set for the current route, the route records are generated for the current route and all its children. The path prefix of the children is matchAs(that is, the path of the alias route).}}Copy the code
- An alias supports single and multiple aliases, that is
route.alias
Support the incoming/foo
or['/foo','/bar']
, so the two cases were normalized and unified into an array - It then iterates through the array, checking aliases and
path
Whether to duplicate, then generate a separate configuration pass for the alias route, and finally calladdRouteRecord
Generate alias routing records- Notice that it also passes
matchAs
Handles scenarios where aliased routes generate child routes, mainly through SettingsmatchAs
forrecord.path || '/'
, and then generates child routing records based onmatchAs
Generates a child record of the alias routing record, as shown aboveNested routines consist of chapters
- Notice that it also passes
- What does an alias route map look like
[{path: '/root'.component: Root, alias: '/root-alias' },
{ path: '/home'.component: Home,
children: [{path: 'bar'.component: Bar, alias: 'bar-alias' },
{ path: 'baz'.component: Baz, alias: ['/baz'.'baz-alias']}]}Copy the code
- alias-route.png
- You can see that a separate routing record is generated for both the alias route and the child routes of the alias route
summary
- The routing record is
Route mapping table
Is an important part of- In the routing record
regex
Is a key field used to process dynamic route parameterspath-to-regexp
Implementation of the
- In the routing record
- Routing records are generated based on the following types of routing records
- Embedded routines by
- A child route generates a single route record
- Alias routing and alias routing subroutes
- An alias route and its subroutes each generate a routing record
- After routing
- Embedded routines by
- The entire process for generating routing records is shown in the following figure
- add-route-record.png
- So we’re done here
VueRouter
Instantiation to create matchers (generate routing mapping tables) related logic - Next we’ll look at generation according to routing patterns
The History instance
Related logic of
Routing patterns
- An important feature of front-end routing is to switch pages without refreshing
- That is, the URL changes, the page does not refresh to realize the page jump
- There are two ways to achieve this
- A kind of
hash+hashChange
“, another useHistory API
thepushState+popState
- The former mainly uses
hash
The page does not refresh and fires when changes are madehashChange
This feature implements front-end routing - The latter made full use of it
HTML5 History API
thepushState
Methods andpopState
Event to implement front-end routing - The comparison
- The former
- Good compatibility,
hashChange
Support to IE8 url
Will carry/ # /
Not beautiful,- No server side modification is required
- Good compatibility,
- The latter
- Compatible to the consumer
url
With normalurl
The same- Due to its
url
With normalurl
Same, so when you refresh, it’s going to use thisurl
Request server page for link, and the server does not have this page, will 404, so need to cooperate with the server to redirect all requests to the home page, the control of the whole route to the front-end route
VueRouter
Three routing modes are supportedhash
,history
,abstract
hash
A pattern is an implementation of the first scenariohistory
A pattern is an implementation of the second scenarioabstract
Mode is used in non-browser environments, mainly forSSR
Core classes
VueRouter
Three routing modes are implemented by the following three core classesHistory
- The base class
- Located in the
src/history/base.js
HTML5History
- Used to support
pushState
The browser src/history/html5.js
- Used to support
HashHistory
- Used when not supported
pushState
The browser src/history/hash.js
- Used when not supported
AbstractHistory
- For non-browser environments (server-side rendering)
src/history/abstract.js
- You can see the relationship between them in the graph below
- route-mode-class.png
HTML5History
,HashHistory
,AbstractHistory
All three inherit from the base classHistory
;- The three are not only accessible
The History class
They also implement the five interfaces declared in the base class that require subclass implementation (Go, Push, REPLACE, ensureURL, getCurrentLocation
) - Due to the
HashHistory
Listening to thehashChange
So there will be one moresetupListeners
methods AbstractHistory
Because it needs to be used in a non-browser environment, there is no history stack, so it can only be passedThe index, the stack
To simulate the- In the previous analysis
VueRouter
When you instantiate the process, you know thatVueRouter
Different routing modes will be instantiated after they are determinedThe History instance
- So let’s see the difference
History
The instantiation process of
The History class
- It is the parent class (base class) from which all other classes inherit
- The code is located in the
src/history/base.js
// src/history/base.js
/ / parent class
export class History {
router: Router
base: string
current: Route
pending: ?Route
cb: (r: Route) = > void
ready: boolean
readyCbs: Array<Function>
readyErrorCbs: Array<Function>
errorCbs: Array<Function>
// implemented by sub-classes
// Need subclasses (HTML5History, HashHistory) to implement the method
+go: (n: number) = > void
+push: (loc: RawLocation) = > void
+replace: (loc: RawLocation) = > void
+ensureURL: (push? :boolean) = > void
+getCurrentLocation: () = > string
constructor (router: Router, base: ?string) {
this.router = router
// Format base to ensure that base begins with a slash
this.base = normalizeBase(base)
// start with a route object that stands for "nowhere"
this.current = START // The route object currently pointed to, default is START; Namely the from
this.pending = null // Record the route to jump to; That is to
this.ready = false
this.readyCbs = []
this.readyErrorCbs = []
this.errorCbs = []
}
...
}
Copy the code
- As you can see, the constructor does the following things
- Save the
router
The instance - Standardization of the
base
To ensure thatbase
Based on/
At the beginning - Initializes the current route pointing, default only
START
Initial routing; When a route jumps,this.current
Represents thefrom
- Initialize the next route during route jump. The default route is
null
; When a route jumps,this.pending
Represents theto
- Some callback-related properties are initialized
- Save the
START
Defined in thesrc/utils/route.js
In the
// src/utils/route.js
// Initial route
export const START = createRoute(null, {
path: '/'
})
Copy the code
The History class
Is instantiated as follows- history.png
HTML5History class
- Let’s see
HTML5History class
It is inherited fromThe History class
- Located in the
src/history/html5.js
export class HTML5History extends History {
constructor (router: Router, base: ?string) {
// Initialize the parent class History
super(router, base)
// Check whether scroll needs to be supported
const expectScroll = router.options.scrollBehavior
const supportsScroll = supportsPushState && expectScroll
// If scroll is supported, initialize the scroll logic
if (supportsScroll) {
setupScroll()
}
// Get the initial location
const initLocation = getLocation(this.base)
// Listen for popState events
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.
// In some browsers, popState is triggered once when the page is opened
// If the initial route is asynchronous, 'popState' will be triggered first, and the initial route will be parsed after completion, resulting in the route not updated
// So avoid
const location = getLocation(this.base)
if (this.current === START && location === initLocation) {
return
}
// If the route address changes, the router jumps, and the scroll is processed after the jump
this.transitionTo(location, route= > {
if (supportsScroll) {
handleScroll(router, route, current, true)}})})}}Copy the code
- You can see that it inherits from
The History class
, so the superclass constructor is called in the constructor (super(router,base)
) - Checks whether scrolling behavior needs to be supported and initializes the rolling-related logic if so
- Listen to the
Popstate event
And, inpopstate
Invokes when triggeredtransitionTo
Method to implement a jump - Notice that an exception scenario is handled here
- In some browsers, opening the page will trigger once
popstate
, if the routing component is asynchronouspopstate
The event is raised, but the asynchronous component is not finished parsing, resulting inroute
There is no update - So we’re masking that
- In some browsers, opening the page will trigger once
- There will be a section on scrolling and routing jumps later
HTML5History class
Is instantiated as follows- h5history.png
HashHistory class
- Located in the
src/history/hash.js
// src/history/hash.js
export class HashHistory extends History {
constructor (router: Router, base: ?string, fallback: boolean) {
// Instantiate the parent class
super(router, base)
// check history fallback deeplinking
// Fallback is true only when mode is history, the browser does not support popState, and the user manually specifies fallback as true. Otherwise, fallback is false
// If rollback is required, change the URL to hash mode (beginning with /#)
// this.base comes from the parent class
if (fallback && checkFallback(this.base)) {
return
}
ensureSlash()
}
}
Copy the code
- It is inherited from
History
, so also calledsuper(router,base)
- Check the
fallback
To see if I need to back up, as we said, the incomingfallback
Only if the user has set ithistory
And they don’t support itpushState
This parameter is valid only when rollback is enabledtrue
- So, at this point, you need to put
history
Patterns ofurl
replacehash
Mode, that is, add on#
The logic is based oncheckFallback
Implementation of the - If it is not
fallback
Is directly calledensureSlash
To ensure thaturl
Based on/
At the beginning of - We look at the
checkFallback
,ensureSlash
implementation
// src/history/hash.js
/** * check the rollback to convert the URL to hash mode (add /#) */
function checkFallback (base) {
const location = getLocation(base)
// If the address does not start with /#, add it
if (!/ # ^ / / /.test(location)) {
window.location.replace(cleanPath(base + '/ #' + location)) // This step implements URL substitution
return true}}/** * make sure the URL starts with /* /
function ensureSlash () :boolean {
const path = getHash()
if (path.charAt(0) = = ='/') {
return true
}
replaceHash('/' + path)
return false
}
// Replace hash records
function replaceHash (path) {
// If pushState is supported, replaceState is preferred
if (supportsPushState) {
replaceState(getUrl(path))
} else {
window.location.replace(getUrl(path))
}
}
Copy the code
- Did you find that
HashHistory
Less scrolling support and listeninghashChange
Relevant logic, and that’s becausehashChange
There are special scenarios that need to waitmounts
Before they can listen in.- The logic of this piece is all in
setupListeners
Method,setupListeners
Will be inVueRouter
callinit
“, which we’ll look at in the initialization section
- The logic of this piece is all in
HashHistory class
Is instantiated as follows- hash-history.png
AbstractHistory class
AbstractHistory
Is intended for non-browser environments- Located in the
src/history/abstract.js
// src/history/abstract.js
/** * Supports all JavaScript runtime environments, such as node.js server. If no browser API is found, the route will automatically force the mode * *@export
* @class AbstractHistory
* @extends {History}* /
export class AbstractHistory extends History {
constructor (router: Router, base: ?string) {
// Initialize the parent class
super(router, base)
this.stack = []
this.index = -1}}Copy the code
- As you can see, its instantiation is the simplest, only the parent class is initialized and the
index
,stack
Initialization is done - As mentioned earlier, in non-browser environments, there is no history stack, so use
index
,stack
To simulate the history stack AbstractHistory class
Is instantiated as follows- abstract-history.png
summary
- In this section we briefly analyze the three routing patterns and look at what is required to implement them
History
How is a class instantiated - To this,
VueRouter
The entire instantiation process is basically covered - Now, let’s summarize it briefly with a diagram
VueRouter
Instantiation process - vue-router-instance.png
Initialization Process
- Now that we’ve analyzed the instantiation process, let’s look at how the initialization works. What did you do
When to call init
- When analyzing the installation process, we know that
VueRouter
Registered a global blend, blendbeforeCreate
hook - The following code
// src/install.js
// Register global mixin
Vue.mixin({
beforeCreate () {
// Check whether vue-router is used
if (isDef(this.$options.router)) {
...
this._router = this.$options.router // Save the VueRouter instance. This.$options.router only exists on the Vue root instance
// beforeCreate is called when the beforeCreate hook is triggered
this._router.init(this) // Initialize the VueRouter instance and pass in the Vue root instance
} else{... }... }, destroyed () { ... }})Copy the code
- We know that global mixing affects all subsequent creation
Vue instance
, sobeforeCreate
The first trigger was inThe Vue root instance is instantiated
namelynew Vue({router})
When,
After the router instance is triggered, the init method of the router instance is called and the Vue root instance is passed to complete the initialization process.
- Due to the
router
Exists only inVue root instance
the$options
So, the entire initialization will only be called once - So let’s see
init
Method implementation
The init method
VueRouter
theinit
Methods insrc/index.js
// src/install.js
export default class VueRouter {
// Initialize app as Vue root instance
init (app: any /* Vue component instance */) {
// Development environment, make sure you have VueRouter installedprocess.env.NODE_ENV ! = ='production' && assert(
install.installed,
`not installed. Make sure to call \`Vue.use(VueRouter)\` ` +
`before creating root instance.`
)
this.apps.push(app) // Save the instance
// set up app destroyed handler
// https://github.com/vuejs/vue-router/issues/2639
// Bind destroyed hook to avoid memory leaks
app.$once('hook:destroyed'.() = > {
// clean out app from this.apps array once destroyed
const index = this.apps.indexOf(app)
if (index > -1) this.apps.splice(index, 1)
// ensure we still have a main app or null if no apps
// we do not release the router so it can be reused
// Make sure there is always a master application
if (this.app === app) this.app = this.apps[0] | |null
})
// main app previously initialized
// return as we don't need to set up new history listener
// If the main app already exists, there is no need to repeat the initialization of the history event listener
if (this.app) {
return
}
this.app = app
const history = this.history
if (history instanceof HTML5History) {
// In the case of HTML5History, the parent class's transitionTo method is called directly to the current location
history.transitionTo(history.getCurrentLocation())
} else if (history instanceof HashHistory) {
// In the case of HashHistory, onComplete, onAbort callback is passed after calling the parent class transitionTo method
const setupHashListener = () = > {
/ / call HashHistory setupListeners method, set the hashchange listening in
// Set the hashchange listener after route switch.
/ / repair at https://github.com/vuejs/vue-router/issues/725
// Because beforeEnter hooks are fired twice if the hook function beforeEnter is asynchronous. Since #/ is added to hash values that do not begin with a/at initialization, the hashchange event is triggered and the lifecycle hook is traversed again, which means the beforeEnter hook function is called again.
history.setupListeners()
}
history.transitionTo(
history.getCurrentLocation(),
setupHashListener, // onComplete callback to transitionTo
setupHashListener // onAbort callback of transitionTo)}// Call the parent class's LISTEN method to add the callback;
// The callback is triggered when the parent class's updateRoute method is called, reassigning app._route
// Since app._route is defined as responsive, app._route changes and components that depend on app._route (route-View components) are re-rendered
history.listen(route= > {
this.apps.forEach((app) = > {
app._route = route
})
})
}
}
Copy the code
- As you can see, the main things are as follows
- Check the
VueRouter
Has been installed? - The mount is saved
The router instances
theVue instance
VueRouter
Supports multi-instance nesting, so it existsthis.apps
To save holdThe router instances
theVue instance
- Registered a one-time hook
destroyed
In thedestroyed
When unloadingthis.app
To avoid memory leaks - Check the
this.app
To avoid repeated event listening - According to the
history
Type, calltransitionTo
Jump to a different initial page - registered
updateRoute
The callback, inrouter
Update when updatedapp._route
Complete the page re-rendering- This is what we’re doing
The view components
We’ll talk about that in more detail
- This is what we’re doing
- Let’s focus on that
transitionTo
Relevant logic
setupListeners
- It says that, at initialization, it’s based on
The history type
, the calltransitionTo
Jump to a different initial page - Why jump to the initial page?
- Because the URL may point to another page during initialization, you need to call it
getCurrentLocation
Method resolves the path from the current URL and jumps to it
- Because the URL may point to another page during initialization, you need to call it
- You can see
HTML5History class
andHashHistory class
calltransitionTo
Methods have different parameters- The former takes only one argument
- The latter takes three arguments
- We look at the
transitionTo
Method’s method signature
// src/history/base.js./ / parent class
export class History {...// Route jump
transitionTo (
location: RawLocation, // Primitive location, either a URL or a location interface(custom shape, defined in types/router.d.ts)onComplete? :Function.// The jump successfully callbackonAbort? :Function// Jump failed callback) {... }}Copy the code
- The first parameter is the address to resolve, the second is the jump success callback, and the third is the jump failure callback
- Let’s see
HashHistory
Why does a class need to pass in a callback - You can see that both the success and failure callbacks passed in are
setupHashListener
The function,setupHashListener
The function is called internallyhistory.setupListeners
Method, and this method isHashHistory class
The unique - Open the
src/history/hash.js
// src/history/hash.js
// this is delayed until the app mounts
// to avoid the hashchange listener being fired too early
/ / repair # 725; https://github.com/vuejs/vue-router/issues/725
// Because beforeEnter hooks are fired twice if the hook function beforeEnter is asynchronous. Since #/ is added to hash values that do not begin with a/at initialization, the hashchange event is triggered and the lifecycle hook is traversed again, which means the beforeEnter hook function is called again.
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
- The main logic is as follows
setupListeners
It mainly determines whether the rolling behavior needs to be supported, and if so, initializes the related logic- And then add
Change the url
Event listening. I said there are two ways to implement routingpushState+popState
,hash+hashChange
- You can see here even though it’s
HashHistory
Will also be used preferentiallypopstate
Event to listenurl
The change of the - when
url
Is called when changes occurtransitionTo
Jump to a new route - You can see the logical sum of this
HTML5History class
The logic handled at instantiation time is similar
// src/history/html5.js
export class HTML5History extends History {
constructor (router: Router, base: ?string) {...// Check whether scroll needs to be supported
const expectScroll = router.options.scrollBehavior
const supportsScroll = supportsPushState && expectScroll
// If scroll is supported, initialize the scroll logic
if (supportsScroll) {
setupScroll()
}
// Get the initial location
const initLocation = getLocation(this.base)
// Listen for popState events
window.addEventListener('popstate'.e= >{...// If the route address changes, the router jumps, and the scroll is processed after the jump
this.transitionTo(location, route= > {
if (supportsScroll) {
handleScroll(router, route, current, true)}})})}}Copy the code
- So why is the timing different?
HTML5Histroy
Listen for events at instantiation timeHashHistory
Listen for events after the initial route jump ends
- This is for repair# 725 problem
- if
beforeEnter
If it’s asynchronous,beforeEnter
It fires twice, and that’s because at initialization,hash
Value is not/
The first words will be filled in# /
, the process will triggerhashchange
Event, so the lifecycle hook goes through again, causing the call againbeforeEnter
Hook function. So you have to puthashChange
Event monitoring is delayed until the initial route jump is complete.
- if
summary
- The following activity diagram is summarized for the init process
- init.png
conclusion
- This paper mainly starts from the whole design idea of front-end routing, analyzes the basic principles of front-end routing design step by step, and clarifies the design idea
- And then I introduced the global
vue-router
Several terms - Having a general idea of the terminology, we went over it
vue-router
How is the directory structure layered - Once you understand the directory structure, let’s start with installation
vue-router
What was done during installation - After the installation, we introduced a few more
The History class
What is the inheritance relationship and how is it instantiated - With the instantiation done, we finally walk through the initialization process
PS
- The rest will be introduced later. If you think it’s ok, you can give it a thumbs up at ✨
- personal
github
github.com/BryanAdamss, 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…
- 🏆 nuggets technical essay | double festival special articles
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/…
communication
- If you have any problems, you can add wechat to communicate and grow together and make progress together