zoukankan      html  css  js  c++  java
  • 组件的 keep-alive 简介

    本篇文章,我们来讲一下keep-alive的实现。  更容易看懂

    Vue中,有三个内置的抽象组件,分别是keep-alivetransitiontransition-group

    它们都有一个共同的特点,就是自身不会渲染一个DOM元素,也不会出现在父组件链中。

    keep-alive的作用,是包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。具体的用法见这里

    该组件的定义,是在src/core/components/keep-alive.js文件中。

    它会在Vue初始化时,添加在Vue.options.components上,所以在所有的组件中,都可以直接只用它

    直接看代码:

    export default {
      name: 'keep-alive',
      abstract: true,
    
      props: {
        ...
      },
    
      created () {
        this.cache = Object.create(null)
      },
    
      destroyed () {
       ...
      },
    
      watch: {
        ...
      },
    
      render () {
        ...
      }
    }

    name不用多说,abstract: true 这个条件我们自己定义组件时通常不会用,

    它是用来标识当前的组件是一个抽象组件,它自身不会渲染一个真实的DOM元素。

    比如在创建两个vm实例之间的父子关系时,会跳过抽象组件的实例:

      let parent = options.parent
    if (parent && !options.abstract) { while (parent.$options.abstract && parent.$parent) { parent = parent.$parent } parent.$children.push(vm) }

    props表示我们可以传入include来匹配哪些组件可以缓存exclude来匹配哪些组件不缓存。

    created钩子函数调用时,会创建一个this.cache对象用于缓存它的子组件。

    destroyed表示keep-alive被销毁时,会同时销毁它缓存的组件,并调用deactivated钩子函数。

    function pruneCacheEntry (vnode: ?VNode) {
      if (vnode) {
        if (!vnode.componentInstance._inactive) {
          callHook(vnode.componentInstance, 'deactivated')
        }
        vnode.componentInstance.$destroy()
      }
    }



    watch是在我们改变props传入的值时,同时对this.cache缓存中的数据进行处理。

    function pruneCache (cache: VNodeCache, filter: Function) {
      for (const key in cache) {
        const cachedNode: ?VNode = cache[key]
        if (cachedNode) {
          const name: ?string = getComponentName(cachedNode.componentOptions)
          if (name && !filter(name)) {
            pruneCacheEntry(cachedNode)
            cache[key] = null
          }
        }
      }
    }


    抽象组件没有实际的DOM元素,所以也就没有template模板,它会有一个render函数,我们就来看看里面进行了哪些操作。

      render () {
        const vnode: VNode = getFirstComponentChild(this.$slots.default)
        const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
        if (componentOptions) {
          // check pattern
          const name: ?string = getComponentName(componentOptions)
          if (name && (
            (this.include && !matches(this.include, name)) ||
            (this.exclude && matches(this.exclude, name))
          )) {
            return vnode
          }
          const key: ?string = vnode.key == null
            // same constructor may get registered as different local components
            // so cid alone is not enough (#3269)
            ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
            : vnode.key
          if (this.cache[key]) {
            vnode.componentInstance = this.cache[key].componentInstance
          } else {
            this.cache[key] = vnode
          }
          vnode.data.keepAlive = true
        }
        return vnode
      }


    首先,调用getFirstComponentChild方法,来获取this.$slots.default中的第一个元素。

    export function getFirstComponentChild (children: ?Array<VNode>): ?VNode {
      return children && children.filter((c: VNode) => c && c.componentOptions)[0]
    }

    this.$slots.default中包含的是什么内容,我们在《slot和作用域插槽》中已经详细的做了讲解。

    从上面的方法我们可以看到,在我们会过滤掉非自定义的标签,然后获取第一个自定义标签所对应的vnode

    所以,如果keep-alive里面包裹的是html标签,是不会渲染的。

    然后获取componentOptions

    vdom——VNode中我们介绍过componentOptions包含五个元素

    { Ctor, propsData, listeners, tag, children }

    function getComponentName (opts: ?VNodeComponentOptions): ?string {
      return opts && (opts.Ctor.options.name || opts.tag)
    }

    通过getComponentName方法来获取组件名,然后判断该组件是否合法,

    如果include不匹配或exclude匹配,则说明该组件不需要缓存,

    此时直接返回该vnode

    否则,vnode.key不存在则生成一个,存在则就用vnode.key作为key

    然后把该vnode添加到this.cache中,并设置vnode.data.keepAlive = true

    最终返回该vnode

    以上只是render函数执行的过程,keep-alive本身也是一个组件

    render函数调用生成vnode后,同样会走__patch__。在创建和diff的过程中,

    也会调用initprepatchinsertdestroy钩子函数。

    不过,每个钩子函数中所做的处理,和普通组件有所不同。

      init (
        vnode: VNodeWithData,
        hydrating: boolean,
        parentElm: ?Node,
        refElm: ?Node
      ): ?boolean {
    if (!vnode.componentInstance || vnode.componentInstance._isDestroyed) { const child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance, parentElm, refElm ) child.$mount(hydrating ? vnode.elm : undefined, hydrating)
    } else if (vnode.data.keepAlive) { // kept-alive components, treat as a patch const mountedNode: any = vnode // work around flow componentVNodeHooks.prepatch(mountedNode, mountedNode) } },

    keep-alive组件内调用__patch__时,如果render返回的vnode是第一次使用,

    则走正常的创建流程,如果之前创建过且添加了vnode.data.keepAlive

    则直接调用prepatch方法,且传入的新旧vnode相同。

      prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
        const options = vnode.componentOptions
        const child = vnode.componentInstance = oldVnode.componentInstance
        updateChildComponent(
          child,
          options.propsData, // updated props
          options.listeners, // updated listeners
          vnode, // new parent vnode
          options.children // new children
        )
      },

    prepatch函数做了哪些工作,之前也详细的介绍过,这里就不多说了。

    简单的总结,就是依据新vnode中的数据,更新组件内容

      insert (vnode: MountedComponentVNode) {
        if (!vnode.componentInstance._isMounted) {
          vnode.componentInstance._isMounted = true
          callHook(vnode.componentInstance, 'mounted')
        }
        if (vnode.data.keepAlive) {
          activateChildComponent(vnode.componentInstance, true /* direct */)
        }
      },

    在组件插入到页面后,如果是vnode.data.keepAlive则会调用activateChildComponent

    这里面主要是调用子组件的activated钩子函数,并设置vm._inactive的标识状态。

      destroy (vnode: MountedComponentVNode) {
        if (!vnode.componentInstance._isDestroyed) {
          if (!vnode.data.keepAlive) {
            vnode.componentInstance.$destroy()
          } else {
            deactivateChildComponent(vnode.componentInstance, true /* direct */)
          }
        }
      }

    在组件销毁时,如果是vnode.data.keepAlive返回true

    则只调用deactivateChildComponent,这里面主要是调用子组件的deactivated钩子函数,

    并设置vm._directInactive的标识状态。因为vnode.data.keepAlivetrue的组件,

    是会被keep-alive缓存起来的,所以不会直接调用它的$destroy()方法,

    上面我们也提到了,当keep-alive组件被销毁时,会触发它缓存中所有组件的$destroy()

    因为keep-alive包裹的组件状态变化,还会触发其子组件的activateddeactivated钩子函数,

    activateChildComponentdeactivateChildComponent也会做一些这方面的处理,细节大家可以自行查看。

  • 相关阅读:
    制造业接入物联网的4大优势
    一篇文章读懂什么是串口通信及其工作原理
    4G工业路由器在仓储物流中的应用
    4G工业路由器应用于远程医疗设备监控
    串口服务器在饮料自动化生产线的应用
    PLC和串口服务器在数字化工厂中的作用
    LoRa集中器在石油勘探等领域的应用解决方案
    4G模块为基础的物联网在精准农业中的应用
    4G工业路由器在垃圾和废水处理的应用案例
    HDU 6205 2017沈阳网络赛 思维题
  • 原文地址:https://www.cnblogs.com/dhsz/p/7690731.html
Copyright © 2011-2022 走看看