一、前言

一些组件需要通过接口请求数据来进行显示,请求数据会造成一定的等待时间,如果反复切换组件,那就需要反复进行数据请求,并且这部分数据返回时相同的;那么可以在组件第一次加载时,将组件对象缓存起来 当切换路由时显示;组件对象被缓存起来,不被销毁,以提升再次查看时的用户体验。
如:Tab页制作的各种状态的订单列表切换

当然,这取决于你的数据是否需要较高的实时性

 二、理解

1) 默认情况下, 被切换的路由组件对象会死亡释放, 再次回来时是重新创建的

2) 如果可以缓存路由组件对象, 可以提高用户体验

三、解决

1.如果想全部实现缓存可以直接用<keep-alive> </keep-alive> 标签将 <router-view> </router-view>包裹起来

<keep-alive>
    <router-view></router-view>
</keep-alive>

2.局部路由的缓存可在路由记录中meta添加属性keepAlive:true,然后通过取$route.meta.keepAlive的值进行判断是否要进行缓存<router-view v-if="$route.meta.keepAlive"></router-view>

查看路由元meta的信息:

从A页面跳转到B页面,缓存A组件;从A组件跳转到C组件,取消缓存,如何实现?

在Vue中,可以通过路由的meta字段来标记需要缓存的组件,在路由配置中设置keepAlive: true。比如:

const router = new VueRouter({
  routes: [
    {
      path: '/A',
      component: A,
      meta: {
        keepAlive: true
      }
    },
    {
      path: '/B',
      component: B
    },
    {
      path: '/C',
      component: C
    }
  ]
})

在需要取消缓存的组件中,调用<keep-alive> </keep-alive> 组件特有的生命周期钩子activated()中调用 $destroy() 方法来销毁该组件实例。比如:

export default {
  activated() {
    this.$destroy();
  }
}

this.$destroy() 作用:

完全销毁一个实例。该阶段所做的主要工作是将当前的Vue实例从其父级实例中删除,取消当前实例上的所有依赖追踪并且移除实例上的所有事件监听器。也就是说,当这个阶段完成之后,当前的Vue实例的整个生命流程就全部走完了,最终“寿终正寝”了。清理它与其它实例的连接,解绑它的全部指令及事件监听器。触发 beforeDestroy 和 destroyed 的钩子。

当销毁一个组件时我们有可能会用这种方法:

this.$destroy()
this.$el.parentNode.removeChild(this.$el) //通过父级元素销毁当前组件

查看页面html该组件确实被销毁, 但是removeChild销毁组件只是表面上的销毁了该组件, 实际上该组件函数任然缓存在vue中,下次组件显示时,组件还是会处于被销毁(destroy)状态,所以数据响应式,生命周期等都会不起作用!

需要真正销毁一个组件可以使用 v-if

this.$destroy() 销毁阶段分析

上文说了,当调用了实例的$destroy方法之后,当前实例就进入了销毁阶段。所以分析销毁阶段就是分析$destroy方法的内部实现。该方法的定义位于源码的src/core/instance.lifecycle.js中,如下:

Vue.prototype.$destroy = function () {
  const vm: Component = this
  if (vm._isBeingDestroyed) {
    return
  }
  callHook(vm, 'beforeDestroy')
  vm._isBeingDestroyed = true
  // remove self from parent
  const parent = vm.$parent
  if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
    remove(parent.$children, vm)
  }
  // teardown watchers
  if (vm._watcher) {
    vm._watcher.teardown()
  }
  let i = vm._watchers.length
  while (i--) {
    vm._watchers[i].teardown()
  }
  // remove reference from data ob
  // frozen object may not have observer.
  if (vm._data.__ob__) {
    vm._data.__ob__.vmCount--
  }
  // call the last hook...
  vm._isDestroyed = true
  // invoke destroy hooks on current rendered tree
  vm.__patch__(vm._vnode, null)
  // fire destroyed hook
  callHook(vm, 'destroyed')
  // turn off all instance listeners.
  vm.$off()
  // remove __vue__ reference
  if (vm.$el) {
    vm.$el.__vue__ = null
  }
  // release circular reference (##6759)
  if (vm.$vnode) {
    vm.$vnode.parent = null
  }
}

可以看到,在上述代码中,首先判断当前实例的_isBeingDestroyed属性是否为true,因为该属性标志着当前实例是否处于正在被销毁的状态,如果它为true,则直接return退出函数,防止反复执行销毁逻辑。如下:

const vm: Component = this
if (vm._isBeingDestroyed) {
  return
}

接着,触发生命周期钩子函数beforeDestroy,该钩子函数的调用标志着当前实例正式开始销毁。如下:

callHook(vm, 'beforeDestroy')

下来,就进入了当前实例销毁的真正逻辑。

首先,需要将当前的Vue实例从其父级实例中删除,如下:

const parent = vm.$parent
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
  remove(parent.$children, vm)
}

上面代码表示:如果当前实例有父级实例,同时该父级实例没有被销毁并且不是抽象组件,那么就将当前实例从其父级实例的$children属性中删除,即将自己从父级实例的子实例列表中删除。

把自己从父级实例的子实例列表中删除之后,接下来就开始将自己身上的依赖追踪和事件监听移除。

我们知道, 实例身上的依赖包含两部分:一部分是实例自身依赖其他数据,需要将实例自身从其他数据的依赖列表中删除;另一部分是实例内的数据对其他数据的依赖(如用户使用$watch创建的依赖),也需要从其他数据的依赖列表中删除实例内数据。所以删除依赖的时候需要将这两部分依赖都删除掉。如下:

// teardown watchers
if (vm._watcher) {
  vm._watcher.teardown()
}
let i = vm._watchers.length
while (i--) {
  vm._watchers[i].teardown()
}

上述代码中,首先执行vm._watcher.teardown()将实例自身从其他数据的依赖列表中删除,teardown方法的作用是从所有依赖向的Dep列表中将自己删除。然后,在前面文章介绍initState函数时我们知道,所有实例内的数据对其他数据的依赖都会存放在实例的_watchers属性中,所以我们只需遍历_watchers,将其中的每一个watcher都调用teardown方法,从而实现移除实例内数据对其他数据的依赖。

接下来移除实例内响应式数据的引用、给当前实例上添加_isDestroyed属性来表示当前实例已经被销毁,同时将实例的VNode树设置为null,如下:

if (vm._data.__ob__) {
  vm._data.__ob__.vmCount--
}
vm._isDestroyed = true
vm.__patch__(vm._vnode, null)

接着,触发生命周期钩子函数destroyed,如下:

callHook(vm, 'destroyed')

最后,调用实例的vm.$off方法(关于该方法在后面介绍实例方法时会详细介绍),移除实例上的所有事件监听器。如下:

vm.$off()

最后,再移除一些相关属性的引用,至此,当前实例算是销毁完毕。

总结:

我们知道了,当调用了实例上的vm.$destory方法后,实例就进入了销毁阶段,在该阶段所做的主要工作是将当前的Vue实例从其父级实例中删除,取消当前实例上的所有依赖追踪并且移除实例上的所有事件监听器。并且对照源码将所做的工作都进行了逐行分析。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐