keep-live原理,react-router如何实现keep-alive

3. keep-live原理,react-router如何实现keep-alive

先说结论:被keep-alive标签包裹的组件在第一次初始化时(渲染从render开始)会被缓存起来(以vnode的形式),再次访问时(actived生命周期)从缓存中读取并从patch阶段开始渲染。
Vue的渲染是从图中render阶段开始的,但keep-alive的渲染是在patch阶段,这是构建组件树(虚拟DOM树),并将VNode转换成真正DOM节点的过程。
keep-live原理,react-router如何实现keep-alive_第1张图片

3.1简单描述从render到patch的过程

  1. vue在渲染的时候先调用原型上的_render函数将组建对象转化为vnode实例;而_render是通过调用createElemntcreateEmptyNode两个函数进行转化
  2. createElement的转化过程会根据不同的情形选择new VNodecreateComponent函数来做VNode实例化
  3. 完成VNode实例化后,Vue调用原型上的_update函数把VNode渲染成为真实DOM。这个过程是过调用__patch__函数完成的

keep-live原理,react-router如何实现keep-alive_第2张图片

3.2 keep-alive组件的渲染

3.2.1 不会生成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
  // ...
}

3.2.2 keep-alive 包裹的组件如何使用缓存?

简单来讲:
第一次加载组件是,将组件实例保存在缓存中,
第二次访问组件时,判断组件实例vnode.componentInstancei.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
      }
    }
  }

  • 首次加载被包裹组件时,由keep-alive.js中的render函数可知,vnode.componentInstance的值是undefined,keepAlive的值是true,因为keep-alive组件作为父组件,它的render函数会先于被包裹组件执行;那么就只执行到i(vnode, false /hydrating/),后面的逻辑不再执行;
  • 再次访问被包裹组件时,vnode.componentInstance的值就是已经缓存的组件实例,那么会执行insert(parentElm, vnode.elm, refElm)逻辑,这样就直接把上一次的DOM插入到了父元素中。

3.3 钩子函数

keep-live原理,react-router如何实现keep-alive_第3张图片

看源码
可以看出,当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)都不再执行。

3.4 actived, deactived

  1. activated:在 keep-alive 组件激活时调用
  2. deactivated:在 keep-alive 组件停用时调用

我们在实际开发项目中会有一些需求,***比如跳转到详情页面时,需要保持列表页的滚动条的位置,返回的时候依然在这个位置,**这样可以提高用户体验,这个时候就可以使用缓存组件 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 的代替,缓存组件销毁的时候要做的一些操作可以放在这个里面。

3.5 keep-alive总结

keep-alive标签包裹的组件在第一次初始化时(渲染从render开始)会被缓存起来(以vnode的形式),再次访问时(actived生命周期)从缓存中读取并从patch阶段开始渲染。
Vue的渲染是从图中render阶段开始的,但keep-alive的渲染是在patch阶段,这是构建组件树(虚拟DOM树),并将VNode转换成真正DOM节点的过程。

3.6 react实现keep-alive

  1. react-keep-alive
  2. react-activation
  3. Portals:将组件渲染到父节点以外的DOM上,可以是内存中的。先通过 document.createElement 在内存中创建一个元素,然后再通过 React.createPoral 把 React 子节点渲染到这个元素上,这样就实现了“空渲染”。再次显示时将这个portal的元素渲染到页面Dom中。
  4. react-keeper
  5. react-router-cache-route
  6. react-live-router
  7. display:none

参考

彻底揭秘keep-alive原理
Vue 全站缓存之 keep-alive
Vue源码解析,keep-alive是如何实现缓存的?
别问我[vue],VNode、elm、context、el是个啥?
vue的Vnode 和 patch机制
Vue3.0 核心源码解读 | KeepAlive 组件:如何让组件在内存中缓存和调度

你可能感兴趣的:(react.js,vue.js,前端)