✅ 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

  • throughObject.defineProperty_routerMounted on theVueThe prototype of the$routerProperties of thegetOn the function. This will get you throughthis.$routerTo invoke the_router. usegetThe 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-viewImplementing 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 parameterAnd whether deep traversal is required
  • The third parameter, re match, match out the path you need

The things to watch out for,requireVariable 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…