✅ means completed
❎ until next time
directory
- ✅ pushState replaceState/popstate parsing
- ✅ Vue-router implementation principle
- ✅ Difference between a route and a router
- ✅ Configure the login based on the routing meta information
- ✅ Set the scrolling behavior
- ✅vue routing keep-alive on demand
- ✅watch monitors route changes
- ✅ How do I detect physical key returns
- ✅ how to make book turning effect
- ✅ How to make an elegant routing partition
- ❎ Automatically generates routes based on directories
- ❎ Generate a page based on routing rules
First understand the browser history principle, in order to better combine vue-Router source code step by step to understand its implementation. You can skip this if you already know.
PushState replaceState/popstate parsing
HTML5 is starting to provide manipulation of the content in the history stack. Through the history. PushState/replaceState address is added to the history stack.
PushState/replaceState () method
PushState () takes three arguments: a state object, a title (currently ignored), and (optionally) a URL. Let’s explain the three parameters in detail:
-
State object – The state object state is a JavaScript object that creates a new history entry with pushState (). Whenever the user navigates to a new state, the POPState event is fired, and the state property of the event contains a copy of the history entry state object.
A state object can be anything that can be serialized. The reason is that Firefox saves the status object on the user’s disk for use when the user restarts the browser, and we have a 640K size limit for the status object after its serialized representation. If you pass pushState() a serialized state object greater than 640K, it will throw an exception. If you need more space, sessionStorage and localStorage are recommended.
-
Title – Firefox currently ignores this parameter, but may use it in the future. Passing an empty string here should be safe against future changes to this method. Alternatively, you can pass a short title for the state of the jump.
-
URL – This parameter defines a new historical URL record. Note that the browser does not load the URL immediately after calling pushState(), but it may load it later in certain circumstances, such as when the user reopens the browser. The new URL does not have to be an absolute path. If the new URL is a relative path, it is treated as relative to the current URL. The new URL must be of the same origin as the current URL, otherwise pushState() will throw an exception. This parameter is optional. The default value is the current URL.
Change the history entry
@clickA
history.pushState({ page: 1 }, ""."a.html");
@clickB
history.pushState({ page: 2 }, ""."b.html");
Copy the code
popstate
When a history entry changes, the POPState event is triggered. If the active history entry was created by a call to history.pushState (), or is affected by a call to history.replacEstate (), the state property of the popState event contains a copy of the state object for the history entry.
Note that a call to history.pushState() or history.replacEstate () does not trigger a popState event. This event is triggered only when a browser action is taken, such as when the user clicks the browser’s back button (or calls history.back() in Javascript code)
The browser rollback button is triggered
window.addEventListener('popstate'.() = >{
console.log(location.href)
})
Copy the code
Vue-router implementation principle
In general, the method of history is used to control the routing of the browser, combined with VUE to achieve data and view update. We have talked about the principle of using history above. Now we will look at it in detail with vue-Router
Install the vue – the router
install.js
- through
Object.defineProperty
将_router
Mounted on theVue
The prototype of the$router
Properties of theget
On the function. This will get you throughthis.$router
To invoke the_router
. useget
The advantage is that, to ensure security, can only read can not be modified$router
.
// This.$router is available in the project
Object.defineProperty(Vue.prototype, '$router', {
get () { return this._routerRoot._router }
})
Copy the code
Then, inject the beforeCreate hook function in vue.mixin. Each component calls registerInstance, listening for _route via vue.util. DefineReactive. This will set the current route each time you enter a new page.
// registerInstance is called in 'beforeCreate'
// Call the registerRouteInstance method in the router-view component
const registerInstance = (vm, callVal) = > {
let i = vm.$options._parentVnode
if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
i(vm, callVal)
}
}
Vue.mixin({
beforeCreate () {
if (isDef(this.$options.router)) {
this._routerRoot = this
this._router = this.$options.router
// Initialize listening popState
// Add this._route = route
this._router.init(this)
// Here is the highlight!!
// Add _route to the listener to trigger the update when history.current is modified
Vue.util.defineReactive(this.'_route'.this._router.history.current)
} else {
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
}
// Register the instance and call the router-view method to change the route value to update the view
registerInstance(this.this)
},
destroyed () {
// Destroy the registered instance because the registered instance is undefined
registerInstance(this)}})Copy the code
router-view
Implementing view updates
Router-view is a functional component. The beforeCreate hook on the page calls registerRouteInstance to modify the current route instance. So when the matched. Instances [name] changes, it will trigger the render update view again.
omponents/view.js
data.registerRouteInstance = (vm, val) = > {
const current = matched.instances[name]
// Register a route instance. If it is equal to the current route, it remains the same. If it is not, the instance is updated
if( (val && current ! == vm) || (! val && current === vm) ) {// Modify the current route instance
matched.instances[name] = val
}
}
Copy the code
Creating a Route object
Create a route createRoute that returns a route object by parsing location and so on
src/util/route.js
export function createRoute (record: ? RouteRecord, location: Location, redirectedFrom? :? Location, router? : VueRouter) :Route {
const stringifyQuery = router && router.options.stringifyQuery
let query: any = location.query || {}
try {
query = clone(query)
} catch (e) {}
const route: Route = {
name: location.name || (record && record.name),
meta: (record && record.meta) || {},
path: location.path || '/'.hash: location.hash || ' ',
query,
params: location.params || {},
fullPath: getFullPath(location, stringifyQuery),
matched: record ? formatMatch(record) : []
}
if (redirectedFrom) {
route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery)
}
return Object.freeze(route)
}
Copy the code
Router-view to implement view rendering, create-route to create routing instances, and how to achieve the combination of VUE and data binding, etc. Due to the length of the problem, no more details of things, interested in you can turn over the source code.
A route is a router
Explain the principle of route and router to clarify the difference, through the source code is easy to see their differences
-
Use (VueRouter) and VueRouter constructors to get an instance of the router. The router is a global object that contains all routes and many key objects and attributes.
-
$route is a route object created by createRoute
$route.path
A string equal to the path of the current routing object, which is resolved to an absolute path, such as “/home/news”.
$route.params
Object that contains key-value pairs of dynamic fragments and fully matched fragments in the route
$route.query
Object that contains key-value pairs of query parameters in the route. For example, for /home/news/detail/01? Favorite =yes, $route.query. Favorite == ‘yes’.
$route.router
The router to which the routing rule belongs (and the component to which it belongs).
$route.matched
Array that contains configuration parameter objects corresponding to all fragments contained in the currently matched path.
$route.name
The name of the current path, or null if no named path is used.
Configure the login through the routing meta information
The principle is to set the auth attribute in the meta of the route, and determine whether meta. Auth is true before entering the route. If it is true, then determine whether you have logged in
const beforeEnter = (to, from, next) = > {
if (to.meta && to.meta.auth) {
// No logins go logins
if(! isLogin()) {const nextPage = (res) = > {
if (res.code === 0) {
next(true)}else {
next(false)}};let targetUrl = location.href.split(The '#') [0] + The '#' + to.fullPath
// Here is your login logic
login({
// The page is displayed after the callback
callback: nextPage,
// Target page, enter target page after login successfully
targetUrl: targetUrl
});
} else {
next(true)}}else {
next(true)}}Copy the code
Login in the Foo component Settings
const routes = [
{
path: '/Foo'.name: 'Foo'.meta: {
auth: true,},component: () = > ('Foo.vue'),}, {path: '/Bar'.name: 'Bar'.component: () = > ('Bar.vue'),},]Copy the code
Set scrolling behavior
Set the scrolling behavior and add the route. If there is a savedPosition, it means that it is the second time to enter and the scrolling has been triggered, so it will scroll to the previously opened position. If it is the first time to enter without savedPosition, it will scroll to the top layer.
const router = new Router({
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { x: 0.y: 0 }
}
},
routes
})
Copy the code
Vue routing keep-alive on demand
when wrapping dynamic components, inactive component instances are cached rather than destroyed. Like
is an abstract component: it does not render a DOM element on its own, nor does it appear in the parent component chain.
When a component is switched within
, its activated and deactivated lifecycle hook functions are executed accordingly.
<! -- View components that need caching -->
<router-view v-if="$route.meta.keepAlive">
</router-view>
</keep-alive>
<! -- View component without cache -->
<router-view v-if=! "" $route.meta.keepAlive">
</router-view>
Copy the code
The routing configuration
const routers = [
{
path: '/list'.name: 'list'.component: () = > import('./views/keep-alive/list.vue'),
meta: {
keepAlive: true}}]Copy the code
Because in our project, there will often be a list of details jump, and then the details return to the list, so we can determine whether it needs to be cached according to the needs of the project. If it is cached, the following situations need to be noted
Watch monitors route changes
Sometimes we need to determine what the page shows by passing parameters to the page, such as #/detail? InfoId =123456, we need to display different contents according to the infoId
We’re used to writing it like this
async created() {
const res = await this.pullData()
}
async pullData () {
return this.$http.get('http://test.m.com/detail', { infoId })
}
Copy the code
When we enter the detail page again through the list, although the infoId has changed infoId=234567, the page has not changed, because the page is keep-alive, created does not trigger again, created only executes once at creation time.
To solve this problem, we need to listen for $route and update the page whenever route changes
watch: {
'$route': {
// Triggered immediately when the page is initialized
immediate: true.handler(to, from) {
// Pull data only when entering the current page
if(to.path === '/detail') {
this.pullData(); }}}}Copy the code
This also causes the following problem: the page will be refreshed when the physical key is returned. Here is how to handle the physical key return
How do I detect physical key returns
Why detect the physical return key? For example, if you have a list page like this, you click to enter a list page or a detail page, and then when you return, the list is refreshed and you can’t find the original location, which is very bad for the user experience. Let’s look at an example.
So how do we optimize it? The idea is to not refresh the data when the user returns to the list page, but only refresh the data when the user actively enters the list. Let’s see what happens
Here is the code that does this. It listens for popState. When the browser returns, popState will be triggered. After setTimeout 0, determine isBack (returned by the browser), if not the browser to refresh the data.
@Component
export default {
data() {
return {
// use the return key to determine whether the return key is used
isBack: false
}
},
created () {
// Set isBack = true for physical keys
this.$_onBack(() = >{
this.isBack = true;
});
},
watch: {
'$route': {
immediate: true.handler(to, from) {
// The route is reset isBack = false every time it enters
this.isBack = false;
if(to.path === '/list') {
// Wait for popState listening to end
setTimeout(() = >{!this.isBack && this.pullData(); })}}}}}Copy the code
The _onBack implementation listens for popState, because vue-router is the history state, and popState is triggered when the browser returns, using this feature to determine whether the browser returns key
_onBack(cb) {
window.addEventListener(
"popstate".(e) = > {
if(typeof cb === 'function') {
if(e.state) {
cb(true)}}},false
);
};
Copy the code
How to make the book turning effect
Vue uses the Transition component of VUE, combined with vue-Router, to do some transition effects on the route. Look at the picture first
<template>
<div class="wrap">
<transition :name="transitionName">
<router-view class="child-view"></router-view>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
transitionName: 'turning-down'}},watch: {
'$route' (to, from) {
if(to.path > from.path) {
// Go to the next page
this.transitionName = 'turning-up';
}else{
// return to the previous page
this.transitionName = 'turning-down'; }}}}</script>
<style scoped lang="scss">
.child-view {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
transition: all 4s ease;
transform-origin: 0% center;
}
.turning-down-enter{
opacity: 1;
transform-origin: left;
transform-style: preserve-3d;
-webkit-transform: perspective(1000px) rotateY(-180deg);
transform: perspective(1000px) rotateY(-180deg);
}
.turning-up-leave-active {
transform-style: preserve-3d;
transform: perspective(1000px) rotateY(-180deg);
z-index: 100;
}
</style>
Copy the code
Configure the routing
export default[{path: '/Home'.name: 'home'.component: () = >
import('.. /views/vue/vue-router/Home.vue'),
children: [{path: '/Home/First'.name: 'Home-First'.component: () = >
import('.. /views/vue/vue-router/First.vue'),}, {path: '/Home/Second'.name: 'Home-Second'.component: () = >
import('.. /views/vue/vue-router/Second.vue'}]}]Copy the code
By listening for route changes on the Home page, you can change the transitionName and switch the Enter /leave-active mode of the Transition component during route switching. Therefore, you can turn the book during route switching.
How to make an elegant routing partition
As the project grows, there may be dozens or even more pages in the project, so how to manage these pages? What we do is we separate routes by function.
For example, we divide five intervals, and each interval has a different number of routes
-- a.js
-- b.js
-- c.js
-- d.js
-- e.js
Copy the code
We need to import these five routes separately and combine them
import a from 'routers/a'
import b from 'routers/b'
import c from 'routers/c'
import d from 'routers/d'
import e from 'routers/e'
const routers = [].concat(a, b, c, d, e)
Copy the code
In the future, every time we create a new partition, we have to manually add the corresponding logic, so it seems very inconvenient, so do we have a good solution?
Here is the routing partition I made using webpack’s require.context method to export all the required paths. Require. context takes three parameters
The first parameter
, matching path directory, (starting from the current directory)Second parameter
And whether deep traversal is requiredThe third parameter
, re match, match out the path you need
The things to watch out for,require
Variable names cannot be exported directly
For example, the following example will report an error
const a = './route/a.js'
// Error: A is not a module
require(a)
Copy the code
So require can only add strings or use string concatenation
const a = 'a.js'
require('./route/' + a)
Copy the code
Webpack will then pack all files under./route/ into modules that you can reference using require
Here is an example of completion
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
const routes = []
const context = require.context('./router'.true./\/[\w]+\.(js|ts)$/)
context.keys().forEach(_= > {
const path = _.replace('/'.' ') routes.push(... require('./router/' + path).routes)
})
export default new Router({
routes: [{path: '/'.redirect: '/Home' },
...routes
]
})
Copy the code
Refer to the article
Juejin. Cn/post / 684490…
Segmentfault.com/a/119000001…
www.cnblogs.com/czy960731/p…