Vue keep-alive的实现原理分析
keep-alive的实现原理
使用vue的时候,想必大家都是用过keep-alive,其作用就是缓存页面以及其状态。使用了这么久vue只知道如何使用但不明白其中原理,昨天翻看实现代码,这里做个笔记。
这里以vue3为例
整个组件的源码为:
const KeepAliveImpl = { name: `KeepAlive`, // Marker for special handling inside the renderer. We are not using a === // check directly on KeepAlive in the renderer, because importing it directly // would prevent it from being tree-shaken. __isKeepAlive: true, props: { include: [String, RegExp, Array], exclude: [String, RegExp, Array], max: [String, Number] }, setup(props: KeepAliveProps, { slots }: SetupContext) { const cache: Cache = new Map() const keys: Keys = new Set() let current: VNode | null = null const instance = getCurrentInstance()! // console.log('instance',instance) // KeepAlive communicates with the instantiated renderer via the "sink" // where the renderer passes in platform-specific functions, 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 sink = instance.sink as KeepAliveSink const { renderer: { move, unmount: _unmount, options: { createElement } }, parentSuspense } = sink const storageContainer = createElement('div') // console.log('sink',sink) sink.activate = (vnode, container, anchor) => { move(vnode, container, anchor, MoveType.ENTER, parentSuspense) queuePostRenderEffect(() => { const component = vnode.component! component.isDeactivated = false if (component.a !== null) { invokeHooks(component.a) } }, parentSuspense) } sink.deactivate = (vnode: VNode) => { move(vnode, storageContainer, null, MoveType.LEAVE, parentSuspense) queuePostRenderEffect(() => { const component = vnode.component! if (component.da !== null) { invokeHooks(component.da) } component.isDeactivated = true }, parentSuspense) } function unmount(vnode: VNode) { // reset the shapeFlag so it can be properly unmounted vnode.shapeFlag = ShapeFlags.STATEFUL_COMPONENT _unmount(vnode, instance, parentSuspense) } function pruneCache(filter?: (name: string) => boolean) { cache.forEach((vnode, key) => { const name = getName(vnode.type as Component) 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. current.shapeFlag = ShapeFlags.STATEFUL_COMPONENT } cache.delete(key) keys.delete(key) } watch( () => [props.include, props.exclude], ([include, exclude]) => { include && pruneCache(name => matches(include, name)) exclude && pruneCache(name => matches(exclude, name)) }, { lazy: true } ) onBeforeUnmount(() => { cache.forEach(unmount) }) return () => { if (!slots.default) { return null } const children = slots.default() let vnode = children[0] if (children.length > 1) { if (__DEV__) { warn(`KeepAlive should contain exactly one component child.`) } current = null return children } else if ( !isVNode(vnode) || !(vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) ) { current = null return vnode } const comp = vnode.type as Component const name = getName(comp) const { include, exclude, max } = props if ( (include && (!name || !matches(include, name))) || (exclude && name && matches(exclude, name)) ) { return vnode } const key = vnode.key == null ? comp : vnode.key const cached = cache.get(key) // clone vnode if it's reused because we are going to mutate it if (vnode.el) { vnode = cloneVNode(vnode) } cache.set(key, vnode) if (cached) { // copy over mounted state vnode.el = cached.el vnode.anchor = cached.anchor vnode.component = cached.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(Array.from(keys)[0]) } } // avoid vnode being unmounted vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE current = vnode return vnode } } }
很容易看出keep-alive其实就是vue自己封装的一个组件,和普通组件一样。
再讲keep-alive组件前先了解下vue组件的整个渲染
大致流程如下
keep-alive生命周期
组件挂载:
调用setupStatefulComponent函数触发组件setup方法,其中组件的setup方法核心代码其实就几行:
return () => { const children = slots.default() let vnode = children[0] cache.set(key, vnode) if (cached) { vnode.el = cached.el vnode.anchor = cached.anchor vnode.component = cached.component vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE keys.delete(key) keys.add(key) } else { keys.add(key) } return vnode }
主要逻辑为三:
1.确认需要渲染的slot、
2.将其状态置入缓存或读取已存在的缓存、
3.返回slot对应的vnode,紧接着调用setupRenderEffect,渲染出dom。
组件更新(slot变化):
当slot变化后,首先会调用keep-alive组件的render即setup的返回函数,逻辑见上面setup方法。紧接着当某个slot卸载时,会调用deactivate函数,当某个slot重新挂载时,则会调用activate函数,核心代码如下:
const storageContainer = createElement('div') sink.activate = (vnode, container, anchor) => { move(vnode, container, anchor, MoveType.ENTER, parentSuspense) queuePostRenderEffect(() => { const component = vnode.component! component.isDeactivated = false if (component.a !== null) { invokeHooks(component.a) } }, parentSuspense) } sink.deactivate = (vnode: VNode) => { move(vnode, storageContainer, null, MoveType.LEAVE, parentSuspense) queuePostRenderEffect(() => { const component = vnode.component! if (component.da !== null) { invokeHooks(component.da) } component.isDeactivated = true }, parentSuspense) }
逻辑也很简单,当组件卸载时,将其移入缓存的dom节点中,调用slot的deactivate生命周期,当组件重新挂载时候,将其移入至挂载的dom节点中。
总结来说,keep-alive实现原理就是将对应的状态放入一个cache对象中,对应的dom节点放入缓存dom中,当下次再次需要渲染时,从对象中获取状态,从缓存dom中移出至挂载dom节点中。
keep-alive的使用总结
在平常开发中,有些组件只需要加载一次,后面的数据将不存在变化,亦或者是组件需要缓存状态,滚动条位置等,这个时候,keep-alive的用处就立刻凸显出来了。
1.App.vue中使用keep-alive
include表示需要缓存的页面,exclude表示不需要缓存的页面,你可以只设置其中一个即可,但两个同时设置的时候,切记exclude优先级高于include,例如a组件在exclude中和include中都存在,那么,a组件是不会被缓存的
<template> <div id="app"> <keep-alive :include="whiteList" :exclude="blackList"> <router-view v-if="isRouterAlive" ></router-view> </keep-alive> </div> </template>
<script> export default { name: 'App', data(){ return{ isRouterAlive:true, whiteList:['styleLibrary','OrderList','SalesData'], blackList:['Footer'], personShow:false, } }, } </script>
2.App.vue中配合router进行使用
<template> <div id="app"> <keep-alive> <router-view v-if="$route.meta.keepAlive"></router-view> <!--缓存组件--> </keep-alive> <router-view v-if="!$route.meta.keepAlive"></router-view> <!--非缓存组件--> </div> </template>
将需要缓存的组件的$route.meta中的keepAlive设置为true,反之为false
{ path:'/login', name:'login', component:resolve=>require(['@/pages/login'],resolve), meta:{ keepAlive:true, title:'登录', savedPosition:true, } },
以上为个人经验,希望能给大家一个参考,也希望大家多多支持猪先飞。
原文出处:https://blog.csdn.net/weixin_38189842/article/details/103999
相关文章
- 这篇文章主要介绍了vue中activated的用法,帮助大家更好的理解和使用vue框架,感兴趣的朋友可以了解下...2021-01-03
基于vue-simple-uploader封装文件分片上传、秒传及断点续传的全局上传插件功能
这篇文章主要介绍了基于vue-simple-uploader封装文件分片上传、秒传及断点续传的全局上传插件,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-02-23- 这篇文章主要介绍了Vue基于localStorage存储信息代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-11-16
- 这篇文章主要介绍了vue 监听 Treeselect 选择项的改变操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-09-01
Antd-vue Table组件添加Click事件,实现点击某行数据教程
这篇文章主要介绍了Antd-vue Table组件添加Click事件,实现点击某行数据教程,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-11-17- 这篇文章主要介绍了vue 实现动态路由的方法,文中示例代码非常详细,帮助大家更好的理解和学习,感兴趣的朋友可以了解下...2020-07-06
- 这篇文章主要介绍了Vue组件跨层级获取组件操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-07-28
- 这篇文章主要介绍了vue 获取到数据但却渲染不到页面上的解决方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-11-19
antdesign-vue结合sortablejs实现两个table相互拖拽排序功能
这篇文章主要介绍了antdesign-vue结合sortablejs实现两个table相互拖拽排序功能,本文通过实例图文相结合给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-01-09- 这篇文章主要介绍了vue treeselect获取当前选中项的label实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-09-01
vuejs element table 表格添加行,修改,单独删除行,批量删除行操作
这篇文章主要介绍了vuejs element table 表格添加行,修改,单独删除行,批量删除行操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-07-18- 这篇文章主要给大家介绍了关于Vue中slot-scope的深入理解,这个教程非常适合初学者,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-04-17
- 最常见的多环境配置,就是开发环境配置,和生产环境配置,本文主要介绍了vue项目多环境配置的实现,感兴趣的可以了解一下...2021-07-20
- 这篇文章主要介绍了Vue 3.0 中 jsx 语法使用,帮助大家更好的理解和使用vue框架,感兴趣的朋友可以了解下...2020-11-13
vue项目页面嵌入代码块vue-prism-editor的实现
这篇文章主要介绍了vue项目页面嵌入代码块vue-prism-editor的实现,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-10-30vue Treeselect下拉树只能选择第N级元素实现代码
这篇文章主要介绍了vue Treeselect下拉树只能选择第N级元素实现代码,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-09-01- 这篇文章主要为大家详细介绍了vue实现同时设置多个倒计时,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-05-20
解决vue的router组件component在import时不能使用变量问题
这篇文章主要介绍了解决vue的router组件component在import时不能使用变量问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-07-27Ant design vue table 单击行选中 勾选checkbox教程
这篇文章主要介绍了Ant design vue table 单击行选中 勾选checkbox教程,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-10-25- 这篇文章主要为大家详细介绍了vue实现div单选多选功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-07-16