vue列表缓存keep-alive

使用场景:

列表查询出约课记录之后,跳转到详情后再返回页面内容刷新,之前搜索的结果就没有了,为了解决这一问题,使用了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', '')

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351

推荐阅读更多精彩内容

  • 一、前言 本文介绍的内容包括: keep-alive用法:动态组件&vue-router keep-alive源码...
    amCow阅读 165,218评论 5 132
  • 1. 概念 keep-alive是vue内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。它自身并不会渲...
    意切阅读 870评论 0 0
  • 官方定义 keep-alive 是 Vue 的内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们...
    书虫和泰迪熊阅读 108评论 0 1
  • keep-alive是Vue.js的一个内置组件。它能够不活动的组件实例保存在内存中,我们来探究一下它的源码实现。...
    zhongmeizhi阅读 2,267评论 0 1
  • 一、前言 本文介绍的内容包括: keep-alive用法:动态组件&vue-router keep-alive源码...
    乙哥驿站阅读 307评论 0 1