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:
- 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
- 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:
to
andfrom
Are not class list pagesto
andfrom
Both are class list pagesto
Is the class list pagefrom
Is the class list page
Now let’s break it down in terms of these four cases
- In the first case,
to
andfrom
Are not class list pages- No cache is required, and all previous caches can be cleared
- In the second case,
to
andfrom
Both are class list pages- if
to
Not infrom
To clear the cache and addto
The cache. - Otherwise, keep
from
Cache, addedto
The cache
- if
- The third case,
to
Is the class list page- if
from
Not into
Delete the cache and addto
The cache - Otherwise, no action is required
- if
- A fourth
- if
to
Not infrom
To clear the cache
- if
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
- The routing configuration is indispensable
name
Property, and thisname
Requirements and Componentsname
The same cacheTo
Priority less thankeepAlive
Therefore, do not set up pages that handle this requirementkeepAlive
- It is possible to set up two pages before caching only when switching between them, but I haven’t found a scenario that works
- 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!!!!!!