美文网首页
vue列表缓存keep-alive

vue列表缓存keep-alive

作者: 厚脸皮的书 | 来源:发表于2021-02-04 14:28 被阅读0次

使用场景:

列表查询出约课记录之后,跳转到详情后再返回页面内容刷新,之前搜索的结果就没有了,为了解决这一问题,使用了keep-alive组件来实现页面缓存。

实现方案:

keep-alive

实现原理:

  • keep-alive是vue2.0提供的用来缓存的内置组件,避免多次加载相同的组建,减少性能消耗。(keep-alive.js)
  • 将需要缓存的VNode节点保存在this.cache中(而不是直接存储DOM结构),在render时,如果VNode的name符合缓存条件,则直接从this.cache中取出缓存的VNode实例进行渲染。
  • keep-alive的渲染是在patch阶段,在已经缓存的情况下不会进入$mount阶段,所以mounted之前的钩子只会执行一次。

keep-alive.js源码:

// src/core/components/keep-alive.js
export default {
    name: 'keep-alive',
    abstract: true, // 判断当前组件虚拟dom是否渲染成真实dom的关键
    props: {
        include: patternTypes, // 缓存白名单
        exclude: patternTypes, // 缓存黑名单
        max: [String, Number] // 缓存的组件
    },
    created() {
        this.cache = Object.create(null) // 缓存虚拟dom
        this.keys = [] // 缓存的虚拟dom的键集合
    },
    destroyed() {
        for (const key in this.cache) {
            // 删除所有的缓存
            pruneCacheEntry(this.cache, key, this.keys)
        }
    },
    mounted() {
        // 实时监听黑白名单的变动
        this.$watch('include', val => {
            pruneCache(this, name => matched(val, name))
        })
        this.$watch('exclude', val => {
            pruneCache(this, name => !matches(val, name))
        })
    },
    render() {
        // 先省略...
    }
}

render函数解读

  • 通过getFirstComponentChild获取第一个组件(vnode);
  • 获取该组件的name(有name的返回name,无name返回标签名);
  • 将这个name通过include、exclude属性进行匹配;
    • 匹配不成功说明不需要缓存,直接返回vnode;
    • 匹配成功后,根据key在this.cache中查找是否已经被缓存过;
      • 如果已缓存过,将缓存的VNode的组件实例componentInsance覆盖到当前vnode上,并返回;
      • 未被缓存则将VNode存储在this.cache中;
render () {
    /* 得到slot插槽中的第一个组件 */
    const vnode: VNode = getFirstComponentChild(this.$slots.default)
    const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
    if (componentOptions) {
        /* 获取组件名称,优先获取组件的name字段,否则是组件的tag */
        const name: ?string = getComponentName(componentOptions)
        /* name不在inlcude中或者在exlude中则直接返回vnode(没有取缓存) */
        if (name && (
        (this.include && !matches(this.include, name)) ||
        (this.exclude && matches(this.exclude, name))
        )) {
            return vnode
        }
        const key: ?string = vnode.key == null
        ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
        : vnode.key
        /* 如果已经做过缓存了则直接从缓存中获取组件实例给vnode,还未缓存过则进行缓存 */
        if (this.cache[key]) {
            vnode.componentInstance = this.cache[key].componentInstance
        } else {
            this.cache[key] = vnode
        }
        /* keepAlive标记位 */
        vnode.data.keepAlive = true
    }
    return vnode
}

渲染阶段

只执行一次的钩子:当vnode.componentInstance和keepAlive为true时,不再进入$mount过程,也就不会执行mounted以及之前的钩子函数(beforeCreated、created、mounted)

// src/core/vdom/create-component.js
const componentVNodeHooks = {
  init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
    if (
      vnode.componentInstance &&
      !vnode.componentInstance._isDestroyed &&
      vnode.data.keepAlive
    ) {
      // kept-alive components, treat as a patch
      const mountedNode: any = vnode // work around flow
      componentVNodeHooks.prepatch(mountedNode, mountedNode)
    } else {
      const child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      )
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
    }
  }
  // ...
}

可重复执行的activated:在patch阶段,最后会执行invokeInsertHook函数,这个函数调用组件实例的insert钩子,insert钩子中调用了activateChildComponent函数,递归执行子组件中的activated钩子函数

// src/core/vdom/patch.js
function invokeInsertHook (vnode, queue, initial) {
    if (isTrue(initial) && isDef(vnode.parent)) {
      vnode.parent.data.pendingInsert = queue
    } else {
      for (let i = 0; i < queue.length; ++i) {
        queue[i].data.hook.insert(queue[i]) // 调用VNode自身的insert钩子函数
      }
    }
}
// src/core/vdom/create-component.js
const componentVNodeHooks = {
  // init()
  insert (vnode: MountedComponentVNode) {
    const { context, componentInstance } = vnode
    if (!componentInstance._isMounted) {
      componentInstance._isMounted = true
      callHook(componentInstance, 'mounted')
    }
    if (vnode.data.keepAlive) {
      if (context._isMounted) {
        queueActivatedComponent(componentInstance)
      } else {
        activateChildComponent(componentInstance, true /* direct */)
      }
    }
  // ...
}

页面缓存的使用方法:

  1. keep-alive组件提供了两个属性include、exclude,可以用逗号隔开的字符串或正则表达式、一个数组来表示。
  2. keep-alive的生命周期函数created、mounted只有创建时会被触发一次
  3. 生命周期钩子有两个activated、deactivated,分别在组件激活、非激活状态时触发
  4. 因为keep-alive将组件缓存起来,不会被销毁和重建,所以不会重新调用created、mounted方法。
  5. 需要重置data数据Object.assign(this.data, this.options.data.call(this))
  6. 从指定路由跳转回来需要刷新的情况,可以结合路由守卫beforeRouterEnter和beforeRouterLeave来区分是否需要刷新
  7. 使用vue-devtool观察组件的缓存状态,灰色的为被缓存起来的状态

pageList → pageDetail → pageList,pageList保存原有状态;pageList → 其他 → pageList,pageList初始化状态

使用方法一

// 使用router的meta属性控制
<keep-alive>
    <router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta || !$route.meta.keepAlive" class="main"></router-view>
// router.js
{
    path: 'pageList',
    name: 'pageList',
    component: () => import('../pages/pageList.vue'),
    meta: {
        // keepAlive是否使用keep-alive组件,isUseCache是否需要缓存
        keepAlive: true, isUseCache: false
    }
}
// pageList.vue
<template>
  <div></div>
</template>
<script>
export default {
    name: 'pageList',
    data() {
        return {}
    },
    activated() {
        // 组件活动状态
        if (!this.$route.meta.isUseCache) {
            // 不需要缓存时需要重置数据
            Object.assign(this.$data, this.$options.data.call(this))
            // 设置为需要缓存
            this.$route.meta.isUseCache = true
        }
    },
    deactivated() {
        // 组件非活跃状态
    },
    beforeRouteEnter(to, from, next) {
        // 从pageA页面跳转到pageB需要缓存,其他页面跳转会pageB不需要缓存
        if(from.name != 'pageDetail'){
            to.meta.isUseCache = false;
        } else {
            to.meta.isUseCache = true
        }
    }, 
}
</script>

pageList → pageDetail → pageList,pageList保存原有状态;pageList → 其他 → pageList,pageList初始化状态

使用方法二:

// 使用keep-alive的prop属性
// cacheList可以存储在vuex中,默认为'pageList'
<keep-alive :include="cacheList">
    <router-view></router-view>
</keep-alive>
// pageList.vue
    beforeRouteLeave (to, from, next) {
        if (to.name !== 'pageDetail') {
            this.$store.dispatch('setCacheList', '')
        }else {
            this.$store.dispatch('setCacheList', 'pageList')
        }
        next()
    }

问题:方法二中,pageList → pageDetail → 其他 → pageList,pageList采用了缓存的数据, 解决:其他页面中的beforeRouteLeave增加 this.$store.dispatch('setCacheList', '')

相关文章

网友评论

      本文标题:vue列表缓存keep-alive

      本文链接:https://www.haomeiwen.com/subject/ueebtltx.html