What’s this? First look at the implementation effect, the source code here

Thank you very much XGHeaven and Jeff_one, this effect should be a page stack, which mainly simulates the native APP navigation, and I only realized part of the function of this effect, the complete function should also have “the same page can have multiple instances, have different states”, For those interested, check out Vue-Navigation, VUe-Page-stack, vue-Nav

scenario

First of all, I couldn’t name my major for this interaction. My general scene at that time was as follows:

  1. At present, there is a small mall, from the home page (Index) can enter the List page (List), the List is an infinite List, now users scroll down, see a certain product seems to like, so click to enter the product details page (Info), after reading more satisfied. Yeah, but shop around, and the user comes back to the list page, ready to scroll down, and what happens if you refresh the user’s previous list? If I’m really upset, it means I need to redo the process, locate the product I just liked, and continue to shop around.
SomePage -> List: List reload List -> Info -> List: List uses cacheCopy the code
  1. When the user determines the product and decides to buy it, he enters the order page (Form). By default, the user enters the historical shipping address on the order page. It seems that the user wants to remind the shipping merchant, so he fills in the remarks, and then checks whether there is any mistake in placing the order. Then click the receiving Address to enter the receiving Address list page (Address). After selecting the Address, we will automatically return to the order page for the user and update the receiving Address. What will happen if there is no remark at this time?
SomePage -> Form: Form reloads Form -> Address -> Form: Form uses cacheCopy the code

The above is a simple example. In fact, this is similar to the problem I had when I was working on mobile (SPA) at my last company, except we were a List of merchants, but the interaction was similar. I solved it directly in the beginning by routing Info as a child page of List and Address as a child page of Form. Implementation is realized, but too troublesome, maintenance trouble 😂, and other partners are not too friendly to the group. Until one time I decided to reconstruct it, there was no good idea before the reconstruction, and then Google had some insight: use keep-alive include implementation, I knew the idea at that time, I did not look down, do their own food and clothing 😁.

To unify the vocabulary, the class List page described below is the List and the class detail page is the Info

Remember on pit

At the beginning, I also stepped on a pit, mainly Vue keep-alive at that time rarely contact, all Copy……

Copy keep-alive = copy keep-alive = copy keep-alive = copy keep-alive = copy keep-alive = copy keep-alive

<keep-alive>
  <router-view v-if="$route.meta.keepAlive"/>
</keep-alive>
<router-view v-if=! "" $route.meta.keepAlive"/>
Copy the code

And then I transformed it into something like this

<keep-alive>
  <router-view v-if="$route.meta.keepAlive"/>
</keep-alive>
<keep-alive :include="include">
  <router-view v-if=! "" $route.meta.keepAlive"/>
</keep-alive>
Copy the code

We control this include by adding a cacheTo field to the route option meta of the class list page, which represents the cache for the specified page, and an array of values:

router.beforeEach((to, from, next) = > {
  if (isPageLikeList(from)) {
    // If from is a class list page
    const fromCacheTo = from.meta.cacheTo
    if (fromCacheTo.indexOf(to.name) > - 1) {
      // If to is the class details page
      // Cache the component corresponding to from
    } else {
      // Remove from cache}}// ...
})
Copy the code

But want to be very beautiful, after writing to find the Bug is more serious, class list page cache old failure, why? This will start from the keep-alive include mechanism, so look at the source code of keep-alive, source code has such a paragraph

  // ...
  mounted () {
    this.$watch('include', val => {
      pruneCache(this, name => matches(val, name))
    })
    this.$watch('exclude', val => {
      pruneCache(this, name => ! matches(val, name)) }) },// ...
Copy the code

Then move on to the callback logic for listening to include

function pruneCache (keepAliveInstance: any, filter: Function) {
  // For include, the filter logic is: true if include contains the component name, false otherwise
  const { cache, keys, _vnode } = keepAliveInstance
  for (const key in cache) {
    constcachedNode: ? VNode = cache[key]if (cachedNode) {
      constname: ? string = getComponentName(cachedNode.componentOptions)// If the component is not in the include scope
      if(name && ! filter(name)) { pruneCacheEntry(cache, key, keys, _vnode) } } } }function pruneCacheEntry (cache: VNodeCache, key: string, keys: Array
       
        , current? : VNode
       ) {
  const cached = cache[key]
  // Destroy the component
  if(cached && (! current || cached.tag ! == current.tag)) { cached.componentInstance.$destroy() }// Clear the component's cache
  cache[key] = null
  remove(keys, key)
}
Copy the code

If the component is not included in the include and the current cache[component name] is cached, pruneCacheEntry is executed to destroy the component and clear its cache.

So what does this do to my logic? According to my previous process, dynamic control include do delete component cache is no problem, but there will be a new problem, because the new operation occurred in the left from the class list page before entering into the class details page, the class list page already exists, then listen for source include only clear the cache in the process of operation.

Given the problem, it’s clear how to solve it, right

implementation

Analysis of the

We can cache the class list page efficiently by placing the include operation before the class list page (router.beforeeach). This is as simple as this: cache any class list page before entering it. When you leave, determine if the new route is the class detail page specified by the class list page, if not, clear the cache. So before realizing, let’s refine the scene again, before simply than toys also toys……

SomePage -> List: List reload // first type List -> Info -> List: List use cache // second type List -> Info -> SomePage -> List: List -> Info -> OtherList -> OtherInfo ->... -> List: List, Info, OtherList... In the third case of caching, when you return OtherList from OtherInfo, the OtherInfo should be cleared from the cache, and so on. That is, the previous cache is not retained on returnCopy the code

Now, let’s make some rough logical decisions based on the example scenario. One point here is clear: we only need to care about to and FROM when switching routes, starting with these two routing objects. Here are 4 examples:

  1. toandfromAre not class list pages
  2. toandfromBoth are class list pages
  3. toIs the class list page
  4. fromIs the class list page

Now let’s break it down in terms of these four cases

  1. In the first case,toandfromAre not class list pages
    • No cache is required, and all previous caches can be cleared
  2. In the second case,toandfromBoth are class list pages
    • iftoNot infromTo clear the cache and addtoThe cache.
    • Otherwise, keepfromCache, addedtoThe cache
  3. The third case,toIs the class list page
    • iffromNot intoDelete the cache and addtoThe cache
    • Otherwise, no action is required
  4. A fourth
    • iftoNot infromTo clear the cache

How to determine if it is a class list page?

[{path: '/list'.name: 'list'.meta: {
      cacheTo: ['info']}// ...
  },
  {
    path: '/info'.name: 'info'.// ...}]Copy the code

As above, maintaining a field such as cacheTo in a route is considered a class list page if the component name is configured

The specific implementation

We’ll make it generic (pluggable) and try to make it less intrusive to the original project. I’ll call it VKeepAliveChain

First of all, if I have to create a store to maintain an include that doesn’t have much pluggable functionality, I can use vue.Observable to handle that

// VKeepAliveChain.js
const state = Vue.observable({
  caches: []})const clearCache = (a)= > {
  if (state.caches.length > 0) {
    state.caches = []
  }
}
const addCache = name= > state.caches.push(name)
Copy the code

To avoid using

directly, we implement a functional component to solve the problem

// VKeepAliveChain.js
export const VKeepAliveChain = {
  install (Vue, options = { key: 'cacheTo'{})const { key } = options

    // Support custom keys
    if (key) {
      cacheKey = key
    }
    
    // Pass through children directly, so keep-alive takes only the first component node
    const component = {
      name: 'VKeepAliveChain'.functional: true,
      render (h, { children }) {
        return h(
          'keep-alive',
          { props: { include: state.caches } },
          children
        )
      }
    }

    Vue.component('VKeepAliveChain', component)
  }
}
Copy the code

Now let’s implement the main logic of cache control because we want to make use of router.beforeeach for as little intrusion as possible, we can merge here.

The beforeEach hook of vue-router is repeatable and executed in the order in which it is registered. Here you can pass the router in vue. use and register a beforeEach hook directly in the module

// VKeepAliveChain.js
const defaultHook = (to, from, next) = > next()
export const mergeBeforeEachHook = (hook = defaultHook) = > {
  return (to, from, next) = > {
    // Cache control logic
    // 1. None of these are class list pages
    // Clear the cache
    // 2. Both are class list pages
    // If 'to' is not in 'from' configuration, clear the cache and add 'to' cache
    // Keep the cache of 'from' and add the cache of 'to'
    // 3. The new route is the class list page
    // If 'from' is not in 'to' configuration, clear the cache and add 'to' cache
    // Otherwise, no action is required
    // 4. Old routes are class list pages
    // If 'to' is not in 'from' configuration, clear the cache

    const toName = to.name
    const toCacheTo = (to.meta || {})[cacheKey]
    const isToPageLikeList = toCacheTo && toCacheTo.length > 0
    const fromName = from.name
    const fromCacheTo = (from.meta || {})[cacheKey]
    const isFromPageLikeList = fromCacheTo && fromCacheTo.length > 0

    if(! isToPageLikeList && ! isFromPageLikeList) { clearCache() }else if (isToPageLikeList && isFromPageLikeList) {
      if (fromCacheTo.indexOf(toName) === - 1) {
        clearCache()
      }
      addCache(toName)
    } else if (isToPageLikeList) {
      if (toCacheTo.indexOf(fromName) === - 1) {
        clearCache()
        addCache(toName)
      }
    } else if (isFromPageLikeList) {
      if (fromCacheTo.indexOf(toName) === - 1) {
        clearCache()
      }
    }
    return hook(to, from, next)
  }
}
Copy the code

Then the whole function is implemented and I can’t send it to the NPM V-keep-alive-chain.

Edible way

First import and register it

// main.js
import { mergeBeforeEachHook, VKeepAliveChain } from 'v-keep-alive-chain'

Vue.use(VKeepAliveChain, {
  key: 'cacheTo' // Optional cacheTo by default
})

// If you have not registered beforeEach
router.beforeEach(mergeBeforeEachHook())

// If there is registration beforeEach
router.beforeEach(mergeBeforeEachHook((to, from, next) = > {
  next()
}))
Copy the code

Then in app.vue (depending on your situation)

<keep-alive>
  <router-view v-if="$route.meta.keepAlive"/>
</keep-alive>
<VKeepAliveChain>
  <router-view v-if=! "" $route.meta.keepAlive"/>
</VKeepAliveChain>
Copy the code

Then configure your requirements in the Router

[{path: '/list'.name: 'list'.meta: {
      cacheTo: ['info']}// ...
  },
  {
    path: '/info'.name: 'info'.// ...}]Copy the code

Then you can have fun

Matters needing attention

  1. The routing configuration is indispensablenameProperty, and thisnameRequirements and ComponentsnameThe same
  2. cacheToPriority less thankeepAliveTherefore, do not set up pages that handle this requirementkeepAlive
  3. It is possible to set up two pages before caching only when switching between them, but I haven’t found a scenario that works
  4. Webpack and VUE-CLI are usually used by me, and the scaffolding is vue-CLI4.0, so I rarely dig into these things. Then I looked at the source code released to THE NPM package, and found that many useless polyfills were typed in, resulting in the package size of Gzip is more than 4Kb, so I haven’t found a solution yet. The friend that knows trouble informs 😂

The article is written faster, if there is any mistake, you can leave a message below

Friends, see here, in the hope that the article has inspired to you, I very welcome technology exchange, if you think the article is useful to you, also please give kid a 👍, at ordinary times I don’t care about these, but because I’m about to resume, need something to the appearance, have no way to resume is too bad, thank you, life will bring you good!!!!!!