0

0

vue3 keepalive线上问题怎么解决

王林

王林

发布时间:2023-05-19 08:04:12

|

1388人浏览过

|

来源于亿速云

转载

1、keepalive功能

  • keepalive是vue3中的一个全局组件

  • keepalive 本身不会渲染出来,也不会出现在dom节点当中,但是它会被渲染为vnode,通过vnode可以跟踪到keepalive中的cache和keys,当然也是在开发环境才可以,build打包以后没有暴露到vnode中(这个还要再确认一下)

  • keepalive 最重要的功能就是缓存组件

  • keepalive 通过LRU缓存淘汰策略来更新组件缓存,可以更有效的利用内存,防止内存溢出,源代码中的最大缓存数max为10,也就是10个组件之后,就开始淘汰最先被缓存的组件了

2、keepalive使用场景

  • 这里先假设一个场景: A页面是首页=====> B页面列表页面(需要缓存的页面)=======> C 详情页 由C详情页到到B页面的时候,要返回到B的缓存页面,包括页面的基础数据和列表的滚动条位置信息 如果由B页面返回到A页面,则需要将B的缓存页清空

    立即学习前端免费学习笔记(深入)”;

  • 上述另外一个场景:进入页面直接缓存,然后就结束了,这个比较简单本文就不讨论了

3、在项目中的使用过程

vue3 keepalive线上问题怎么解决

keepalive组件总共有三个参数

  • include:可传字符串、正则表达式、数组,名称匹配成功的组件会被缓存

  • exclude:可传字符串、正则表达式、数组,名称匹配成功的组件不会被缓存

  • max:可传数字,限制缓存组件的最大数量,默认为10

首先在App.vue根代码中添加引入keepalive组件,通过这里可以发现,我这里缓存的相当于整个页面,当然你也可以进行更细粒度的控制页面当中的某个区域组件

    
    

通过App.vue可以发现,通过pinia(也就是vue2中使用的vuex)保存要缓存的页面组件, 来处理include缓存,和保存页面组件中的滚动条信息数据

    import { defineStore } from "pinia";
    export const useKeepAliverStore = defineStore("useKeepAliverStore", {
        state: () => ({
            caches: [] as any,
            scrollList: new Map(),  // 缓存页面组件如果又滚动条的高度
        }),
        actions: {
            add(name: string) {
                this.caches.push(name);
            },
            remove(name: string) {
                console.log(this.caches, 'this.caches')
                this.caches = this.caches.filter((item: any) => item !== name);
                console.log(this.caches, 'this.caches')
            },
            clear() {
                this.caches = []
            }
        }
    });

组件路由刚刚切换时,通过beforeRouteEnter将组件写入include, 此时组件生命周期还没开始。如果都已经开始执行组件生命周期了,再写入就意义了。

所以这个钩子函数就不能写在setup中,要单独提出来写。当然你也可以换成路由的其他钩子函数处理beforeEach,但这里面使用的话,好像使用不了pinia,这个还需要进一步研究一下。

    import { useRoute, useRouter, onBeforeRouteLeave } from "vue-router";
    import { useKeepAliverStore } from "@/store";
    const useStore = useKeepAliverStore()
    export default {
        name:"record-month",
        beforeRouteEnter(to, from, next) {
            next(vm => {
                if(from.name === 'Home' && to.name === 'record-month') {
                useStore.add(to.name)
                }
            });
        }
    }
    

组件路由离开时判断,是否要移出缓存,这个钩子就直接写在setup中就可以了。

    onBeforeRouteLeave((to, from) => {
        console.log(to.name, "onBeforeRouteLeave");
        if (to.name === "new-detection-detail") {
            console.log(to, from, "进入详情页面不做处理");
        } else {
            useStore.remove(from.name)
            console.log(to, from, "删除组件缓存");
        }
    });

在keepalive两个钩子函数中进行处理scroll位置的缓存,onActivated中获取缓存中的位置, onDeactivated记录位置到缓存

    onActivated(() => {
        if(useStore.scrollList.get(routeName)) {
            const top = useStore.scrollList.get(routeName)
            refList.value.setScrollTop(Number(top))
        }
    });
    onDeactivated(() => {
        const top = refList.value.getScrollTop()
        useStore.scrollList.set(routeName, top)
    });

这里定义一个方法,设置scrollTop使用了原生javascript的api

Thiings
Thiings

免费的拟物化图标库

下载
    const setScrollTop = (value: any) => {
        const dom = document.querySelector('.van-pull-refresh')
        dom!.scrollTop = value
    }

同时高度怎么获取要先注册scroll事件,然后通过getScrollTop 获取当前滚动条的位置进行保存即可

    onMounted(() => {
        scrollDom.value = document.querySelector('.van-pull-refresh') as HTMLElement
        const throttledFun = useThrottleFn(() => {
            console.log(scrollDom.value?.scrollTop, 'addEventListener')
            state.scrollTop = scrollDom.value!.scrollTop
        }, 500)
        if(scrollDom.value) {
            scrollDom.value.addEventListener('scroll',throttledFun)
        }
    })
    const getScrollTop = () => {
        console.log('scrollDom.vaue', scrollDom.value?.scrollTop)
        return state.scrollTop
    }

上面注册scroll事件中使用了一个useThrottleFn,这个类库是@vueuse/core中提供的,其中封装了很多工具都非常不错,用兴趣的可以研究研究

    https://vueuse.org/shared/usethrottlefn/#usethrottlefn

此时也可以查看找到实例的vnode查找到keepalive,是在keepalive紧挨着的子组件里

    const instance = getCurrentInstance()
    console.log(instance.vnode.parent) // 这里便是keepalive组件vnode
    // 如果是在开发环境中可以查看到cache对象
    instance.vnode.parent.__v_cache
    // vue源码中,在dev环境对cache进行暴露,生产环境是看不到的
    if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
        ;(instance as any).__v_cache = cache
    }

4、vue3 keepalive源码调试

1、克隆代码

    git clone git@github.com:vuejs/core.git

2、安装依赖

    pnpm i

3、如果不能使用pnpm,可以先通过npm安装一下

    npm i pnpm -g

4、安装完成以后,找到根目录package.json文件中的scripts

    // 在dev命令后添加 --source-map是从已转换的代码,映射到原始的源文件
    "dev": "node scripts/dev.js  --sourcemap"

参考 https://www.yisu.com/article/154583.htm

5、执行pnpm run dev则会build vue源码

    pnpm run dev
    //则会出现以下,代表成功了(2022年5月27日),后期vue源代码作者可能会更新,相应的提示可能发生变更,请注意一下
    > @3.2.36 dev H:\github\sourceCode\core
    > node scripts/dev.js  --sourcemap
    watching: packages\vue\dist\vue.global.js
    //到..\..\core\packages\vue\dist便可以看到编译成功,以及可以查看到examples样例demo页面

6、然后在 ....\core\packages\vue\examples\composition中添加一个aehyok.html文件,将如下代码进行拷贝,然后通过chrome浏览器打开,F12,找到源代码的Tab页面,通过快捷键Ctrl+ P 输入KeepAlive便可以找到这个组件,然后通过左侧行标右键就可以添加断点,进行调试,也可以通过右侧的【调用堆栈】进行快速跳转代码进行调试。

    
    
    
    
    
    
Hello World
Hello World
Hello World

7、调试源码发现 keepalive中的render函数(或者说时setup中的return 函数)在子组件切换时就会去执行,变更逻辑缓存

  • 第一次进入页面初始化keepalive组件会执行一次,

  • 然后点击组件一,再次执行render函数

  • 然后点击组件二,会再次执行render函数

8、调试截图说明

vue3 keepalive线上问题怎么解决

5、vue3 keealive源码粗浅分析

通过查看vue3 KeepAlive.ts源码

    // 在setup初始化中,先获取keepalive实例
    // getCurrentInstance() 可以获取当前组件的实例
    const instance = getCurrentInstance()!
    // KeepAlive communicates with the instantiated renderer via the
    // ctx where the renderer passes in its internals,
    // and the KeepAlive instance exposes activate/deactivate implementations.
    // The whole point of this is to avoid importing KeepAlive directly in the
    // renderer to facilitate tree-shaking.
    const sharedContext = instance.ctx as KeepAliveContext
    // if the internal renderer is not registered, it indicates that this is server-side rendering,
    // for KeepAlive, we just need to render its children
    /// SSR 判断,暂时可以忽略掉即可。
    if (__SSR__ && !sharedContext.renderer) {
        return () => {
            const children = slots.default && slots.default()
            return children && children.length === 1 ? children[0] : children
        }
    }
    // 通过Map存储缓存vnode,
    // 通过Set存储缓存的key(在外面设置的key,或者vnode的type)
    const cache: Cache = new Map()
    const keys: Keys = new Set()
    let current: VNode | null = null
    if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
    ;(instance as any).__v_cache = cache
    }
    const parentSuspense = instance.suspense
    const {
    renderer: {
        p: patch,
        m: move,
        um: _unmount,
        o: { createElement }
    }
    } = sharedContext
    // 创建了隐藏容器
    const storageContainer = createElement('div')
    // 在实例上注册两个钩子函数 activate,  deactivate
    sharedContext.activate = (vnode, container, anchor, isSVG, optimized) => {
        const instance = vnode.component!
        move(vnode, container, anchor, MoveType.ENTER, parentSuspense)
        // in case props have changed
        patch(
            instance.vnode,
            vnode,
            container,
            anchor,
            instance,
            parentSuspense,
            isSVG,
            vnode.slotScopeIds,
            optimized
        )
        queuePostRenderEffect(() => {
            instance.isDeactivated = false
            if (instance.a) {
            invokeArrayFns(instance.a)
            }
            const vnodeHook = vnode.props && vnode.props.onVnodeMounted
            if (vnodeHook) {
            invokeVNodeHook(vnodeHook, instance.parent, vnode)
            }
        }, parentSuspense)
        if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
            // Update components tree
            devtoolsComponentAdded(instance)
        }
    }
    sharedContext.deactivate = (vnode: VNode) => {
        const instance = vnode.component!
        move(vnode, storageContainer, null, MoveType.LEAVE, parentSuspense)
        queuePostRenderEffect(() => {
            if (instance.da) {
            invokeArrayFns(instance.da)
            }
            const vnodeHook = vnode.props && vnode.props.onVnodeUnmounted
            if (vnodeHook) {
            invokeVNodeHook(vnodeHook, instance.parent, vnode)
            }
            instance.isDeactivated = true
        }, parentSuspense)
        if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
            // Update components tree
            devtoolsComponentAdded(instance)
        }
    }
    // 组件卸载
    function unmount(vnode: VNode) {
        // reset the shapeFlag so it can be properly unmounted
        resetShapeFlag(vnode)
        _unmount(vnode, instance, parentSuspense, true)
    }
    // 定义 include和exclude变化时,对缓存进行动态处理
    function pruneCache(filter?: (name: string) => boolean) {
        cache.forEach((vnode, key) => {
            const name = getComponentName(vnode.type as ConcreteComponent)
            if (name && (!filter || !filter(name))) {
            pruneCacheEntry(key)
            }
        })
    }
    function pruneCacheEntry(key: CacheKey) {
        const cached = cache.get(key) as VNode
        if (!current || cached.type !== current.type) {
            unmount(cached)
        } else if (current) {
            // current active instance should no longer be kept-alive.
            // we can't unmount it now but it might be later, so reset its flag now.
            resetShapeFlag(current)
        }
        cache.delete(key)
        keys.delete(key)
    }
    // 可以发现通过include 可以配置被显示的组件,
    // 当然也可以设置exclude来配置不被显示的组件,
    // 组件切换时随时控制缓存
    watch(
    () => [props.include, props.exclude],
    ([include, exclude]) => {
        include && pruneCache(name => matches(include, name))
        exclude && pruneCache(name => !matches(exclude, name))
    },
    // prune post-render after `current` has been updated
    { flush: 'post', deep: true }
    )
    // 定义当前组件Key
    // cache sub tree after render
        let pendingCacheKey: CacheKey | null = null
        // 这是一个重要的方法,设置缓存
        const cacheSubtree = () => {
        // fix #1621, the pendingCacheKey could be 0
        if (pendingCacheKey != null) {
            cache.set(pendingCacheKey, getInnerChild(instance.subTree))
        }
        }
        onMounted(cacheSubtree)
        onUpdated(cacheSubtree)
        // 组件卸载的时候,对缓存列表进行循环判断处理
        onBeforeUnmount(() => {
            cache.forEach(cached => {
                const { subTree, suspense } = instance
                const vnode = getInnerChild(subTree)
                if (cached.type === vnode.type) {
                // current instance will be unmounted as part of keep-alive's unmount
                resetShapeFlag(vnode)
                // but invoke its deactivated hook here
                const da = vnode.component!.da
                da && queuePostRenderEffect(da, suspense)
                return
                }
                unmount(cached)
            })
        })
    // 同时在keepAlive组件setup生命周期中,return () => {} 渲染的时候,对组件进行判断逻辑处理,同样对include和exclude判断渲染。
    // 判断keepalive组件中的子组件,如果大于1个的话,直接警告处理了
    // 另外如果渲染的不是虚拟dom(vNode),则直接返回渲染即可。
    return () => {
        // eslint-disable-next-line no-debugger
        console.log(props.include, 'watch-include')
        pendingCacheKey = null
        if (!slots.default) {
            return null
        }
        const children = slots.default()
        const rawVNode = children[0]
        if (children.length > 1) {
            if (__DEV__) {
            warn(`KeepAlive should contain exactly one component child.`)
            }
            current = null
            return children
        } else if (
            !isVNode(rawVNode) ||
            (!(rawVNode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) &&
            !(rawVNode.shapeFlag & ShapeFlags.SUSPENSE))
        ) {
            current = null
            return rawVNode
        }
        // 接下来处理时Vnode虚拟dom的情况,先获取vnode
        let vnode = getInnerChild(rawVNode)
        // 节点类型
        const comp = vnode.type as ConcreteComponent
        // for async components, name check should be based in its loaded
        // inner component if available
        // 获取组件名称
        const name = getComponentName(
            isAsyncWrapper(vnode)
            ? (vnode.type as ComponentOptions).__asyncResolved || {}
            : comp
        )
        //这个算是最熟悉的通过props传递进行的参数,进行解构
        const { include, exclude, max } = props
        // include判断 组件名称如果没有设置, 或者组件名称不在include中,
        // exclude判断 组件名称有了,或者匹配了
        // 对以上两种情况都不进行缓存处理,直接返回当前vnode虚拟dom即可。
        if (
            (include && (!name || !matches(include, name))) ||
            (exclude && name && matches(exclude, name))
        ) {
            current = vnode
            return rawVNode
        }
        // 接下来开始处理有缓存或者要缓存的了
        // 先获取一下vnode的key设置,然后看看cache缓存中是否存在
        const key = vnode.key == null ? comp : vnode.key
        const cachedVNode = cache.get(key)
        // 这一段可以忽略了,好像时ssContent相关,暂时不管了,没看明白??
        // clone vnode if it's reused because we are going to mutate it
        if (vnode.el) {
            vnode = cloneVNode(vnode)
            if (rawVNode.shapeFlag & ShapeFlags.SUSPENSE) {
            rawVNode.ssContent = vnode
            }
        }
        // 上面判断了,如果没有设置key,则使用vNode的type作为key值
        pendingCacheKey = key
        //判断上面缓存中是否存在vNode
        // if 存在的话,就将缓存中的vnode复制给当前的vnode
        // 同时还判断了组件是否为过渡组件 transition,如果是的话 需要注册过渡组件的钩子
        // 同时先删除key,然后再重新添加key
        // else 不存在的话,就添加到缓存即可
        // 并且要判断一下max最大缓存的数量是否超过了,超过了,则通过淘汰LPR算法,删除最旧的一个缓存
        // 最后又判断了一下是否为Suspense。也是vue3新增的高阶组件。
        if (cachedVNode) {
            // copy over mounted state
            vnode.el = cachedVNode.el
            vnode.component = cachedVNode.component
            if (vnode.transition) {
            // recursively update transition hooks on subTree
            setTransitionHooks(vnode, vnode.transition!)
            }
            // avoid vnode being mounted as fresh
            vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE
            // make this key the freshest
            keys.delete(key)
            keys.add(key)
        } else {
            keys.add(key)
            // prune oldest entry
            if (max && keys.size > parseInt(max as string, 10)) {
            pruneCacheEntry(keys.values().next().value)
            }
        }
        // avoid vnode being unmounted
        vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
        current = vnode
        return isSuspense(rawVNode.type) ? rawVNode : vnode

相关文章

keep
keep

Keep是一款健身安排,无论是想减肥塑形或增肌,还是寻找健身跑步瑜伽计步等训练计划,你可以随时随地选择课程进行训练!权威教练视频教学,健身干货自由分享!有需要的小伙伴快来保存下载体验吧!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
Java编译相关教程合集
Java编译相关教程合集

本专题整合了Java编译相关教程,阅读专题下面的文章了解更多详细内容。

11

2026.01.21

C++多线程相关合集
C++多线程相关合集

本专题整合了C++多线程相关教程,阅读专题下面的的文章了解更多详细内容。

4

2026.01.21

无人机驾驶证报考 uom民用无人机综合管理平台官网
无人机驾驶证报考 uom民用无人机综合管理平台官网

无人机驾驶证(CAAC执照)报考需年满16周岁,初中以上学历,身体健康(矫正视力1.0以上,无严重疾病),且无犯罪记录。个人需通过民航局授权的训练机构报名,经理论(法规、原理)、模拟飞行、实操(GPS/姿态模式)及地面站训练后考试合格,通常15-25天拿证。

16

2026.01.21

Python多线程合集
Python多线程合集

本专题整合了Python多线程相关教程,阅读专题下面的文章了解更多详细内容。

1

2026.01.21

java多线程相关教程合集
java多线程相关教程合集

本专题整合了java多线程相关教程,阅读专题下面的文章了解更多详细内容。

4

2026.01.21

windows激活码分享 windows一键激活教程指南
windows激活码分享 windows一键激活教程指南

Windows 10/11一键激活可以通过PowerShell脚本或KMS工具实现永久或长期激活。最推荐的简便方法是打开PowerShell(管理员),运行 irm https://get.activated.win | iex 脚本,按提示选择数字激活(选项1)。其他方法包括使用HEU KMS Activator工具进行智能激活。

2

2026.01.21

excel表格操作技巧大全 表格制作excel教程
excel表格操作技巧大全 表格制作excel教程

Excel表格操作的核心技巧在于 熟练使用快捷键、数据处理函数及视图工具,如Ctrl+C/V(复制粘贴)、Alt+=(自动求和)、条件格式、数据验证及数据透视表。掌握这些可大幅提升数据分析与办公效率,实现快速录入、查找、筛选和汇总。

6

2026.01.21

毒蘑菇显卡测试网站入口 毒蘑菇测试官网volumeshader_bm
毒蘑菇显卡测试网站入口 毒蘑菇测试官网volumeshader_bm

毒蘑菇VOLUMESHADER_BM测试网站网址为https://toolwa.com/vsbm/,该平台基于WebGL技术通过渲染高复杂度三维分形图形评估设备图形处理能力,用户可通过拖动彩色物体观察画面流畅度判断GPU与CPU协同性能;测试兼容多种设备,但中低端手机易卡顿或崩溃,高端机型可能因发热降频影响表现,桌面端需启用独立显卡并使用支持WebGL的主流浏览器以确保准确结果

25

2026.01.21

github中文官网入口 github中文版官网网页进入
github中文官网入口 github中文版官网网页进入

github中文官网入口https://docs.github.com/zh/get-started,GitHub 是一种基于云的平台,可在其中存储、共享并与他人一起编写代码。 通过将代码存储在GitHub 上的“存储库”中,你可以: “展示或共享”你的工作。 持续“跟踪和管理”对代码的更改。

7

2026.01.21

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Vue3.x 工具篇--十天技能课堂
Vue3.x 工具篇--十天技能课堂

共26课时 | 1.4万人学习

Vue3.x 核心篇--十天技能课堂
Vue3.x 核心篇--十天技能课堂

共30课时 | 1.5万人学习

Vue3.x新特性篇--十天基础课堂
Vue3.x新特性篇--十天基础课堂

共20课时 | 1.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号