先说结论:被
keep-alive
标签包裹的组件在第一次初始化时(渲染从render开始)会被缓存起来(以vnode的形式),再次访问时(actived生命周期)从缓存中读取并从patch阶段开始渲染。
Vue的渲染是从图中render阶段开始的,但keep-alive的渲染是在patch阶段,这是构建组件树(虚拟DOM树),并将VNode转换成真正DOM节点的过程。
_render
函数将组建对象转化为vnode
实例;而_render
是通过调用createElemnt
和createEmptyNode
两个函数进行转化createElement
的转化过程会根据不同的情形选择new VNode
或createComponent
函数来做VNode实例化_update
函数把VNode渲染成为真实DOM。这个过程是过调用__patch__
函数完成的。<keep-alive></keep-alive>
不会生成DOM节点,其实现原理如下Vue在初始化生命周期的时候,为组件实例建立父子关系会根据abstract属性决定是否忽略某个组件。在keep-alive中,设置了abstract: true,那Vue就会跳过该组件实例。
// src/core/instance/lifecycle.js
export function initLifecycle (vm: Component) {
const options = vm.$options
// 找到第一个非abstract的父组件实例
let parent = options.parent
/** keep-alive 设置abstract:true,跳过创建组件实例 **/
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
vm.$parent = parent
// ...
}
简单来讲:
第一次加载组件是,将组件实例保存在缓存中,
第二次访问组件时,判断组件实例vnode.componentInstance
和i.keepAlive
是否为true,如果是直接将缓存的组件实例插入到父节点:insert(parentElm, vnode.elm, refElm) // 将缓存的DOM(vnode.elm)插入父元素中
看源码:
在patch阶段,会执行createComponent函数:
// src/core/vdom/patch.js
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */)
}
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue)
insert(parentElm, vnode.elm, refElm) // 将缓存的DOM(vnode.elm)插入父元素中
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true
}
}
}
看源码
可以看出,当vnode.componentInstance和keepAlive同时为true值时,不再进入$mount过程,那mounted之前的所有钩子函数(beforeCreate、created、mounted)都不再执行。
被缓存的组件实例会为其设置keepAlive = true,而在初始化组件钩子函数中:
// 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)
}
}
// ...
}
可以看出,当vnode.componentInstance和keepAlive同时为true值时,不再进入$mount过程,那mounted之前的所有钩子函数(beforeCreate、created、mounted)都不再执行。
我们在实际开发项目中会有一些需求,***比如跳转到详情页面时,需要保持列表页的滚动条的位置,返回的时候依然在这个位置,**这样可以提高用户体验,这个时候就可以使用缓存组件 keep-alive 来解决。
设置了 keep-alive 缓存的组件,会多出两个生命周期钩子:
首次进入组件时:beforeRouteEnter > beforeCreate > created > mounted > activated > ... ... > beforeRouteLeave > deactivated
再次进入组件时:beforeRouteEnter > activated > ... ... > beforeRouteLeave > deactivated
可以看到,缓存的组件中 activated
钩子函数每次都会触发,所以可以通过这个钩子判断,当前组件时需要使用缓存的数据还是重新调用接口加载数据。如果未使用keep-alive 组件,则在页面回退时会重新渲染页面,首次进入组件的一系列生命周期也会一一被触发。
离开组件时,使用了 keep-alive 不会调用 beforeDestroy 和 destroyed 钩子,因为组件没被销毁,被缓存起来了。所以 deactivated
这个钩子可以看作是 beforeDestroy 和 destroyed 的代替,缓存组件销毁的时候要做的一些操作可以放在这个里面。
被keep-alive
标签包裹的组件在第一次初始化时(渲染从render开始)会被缓存起来(以vnode的形式),再次访问时(actived生命周期)从缓存中读取并从patch阶段开始渲染。
Vue的渲染是从图中render阶段开始的,但keep-alive的渲染是在patch阶段,这是构建组件树(虚拟DOM树),并将VNode转换成真正DOM节点的过程。
参考
彻底揭秘keep-alive原理
Vue 全站缓存之 keep-alive
Vue源码解析,keep-alive是如何实现缓存的?
别问我[vue],VNode、elm、context、el是个啥?
vue的Vnode 和 patch机制
Vue3.0 核心源码解读 | KeepAlive 组件:如何让组件在内存中缓存和调度