Vue3核心源码分析 (五) : 内置组件

源代码 2024-9-21 07:45:50 82 0 来自 中国
  <keep-alive>是Vue.js的一个内置组件,可以使被包罗的组件保存状态或制止重新渲染。下面来分析源码runtime-core/src/components/KeepAlive.ts的实现原理。
  在setup方法中会创建一个缓存容器和缓存的key列表,其代码如下:
  setup(props: KeepAliveProps, { slots }: SetupContext) {// keep-alive组件的上下文对象    const instance = getCurrentInstance()!    // KeepAlive communicates with the instantiated renderer via the    // ctx where the renderer passes in its internals,    // 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 sharedContext = instance.ctx as KeepAliveContext    // if the internal renderer is not registered, it indicates that this is server-side rendering,    // for KeepAlive, we just need to render its children    if (__SSR__ && !sharedContext.renderer) {      return () => {        const children = slots.default && slots.default()        return children && children.length === 1 ? children[0] : children      }    }/* 缓存对象 */    const cache: Cache = new Map()    const keys: Keys = new Set()       // 替换内容       sharedContext.activate = (vnode, container, anchor, isSVG, optimized) => {         const instance = vnode.component!         move(vnode, container, anchor, MoveType.ENTER, parentSuspense)         // 处置惩罚props改变         patch(         ...         )         ...       }       // 替换内容       sharedContext.deactivate = (vnode: VNode) => {         const instance = vnode.component!         move(vnode, storageContainer, null, MoveType.LEAVE, parentSuspense)         ...       }     }  <keep-alive>本身实现了render方法,并没有使用Vue内置的render方法(颠末<template>内容提取、转换AST、render字符串等一系列过程),在实行<keep-alive>组件渲染时,就会实行这个render方法:
    return () => {      pendingCacheKey = null      if (!slots.default) {        return null      }// 得到插槽中的第一个组件      const children = slots.default()      const rawVNode = children[0]      if (children.length > 1) {        if (__DEV__) {          warn(`KeepAlive should contain exactly one component child.`)        }        current = null        return children      } else if (        !isVNode(rawVNode) ||        (!(rawVNode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) &&          !(rawVNode.shapeFlag & ShapeFlags.SUSPENSE))      ) {        current = null        return rawVNode      }      let vnode = getInnerChild(rawVNode)      const comp = vnode.type as ConcreteComponent      // for async components, name check should be based in its loaded      // inner component if available // 获取组件名称,优先获取组件的name字段      const name = getComponentName(        isAsyncWrapper(vnode)          ? (vnode.type as ComponentOptions).__asyncResolved || {}          : comp      ) // name不在include中大概exclude中,则直接返回vnode(没有存取缓存)      const { include, exclude, max } = props      if (        (include && (!name || !matches(include, name))) ||        (exclude && name && matches(exclude, name))      ) {        current = vnode        return rawVNode      }      const key = vnode.key == null ? comp : vnode.key      const cachedVNode = cache.get(key)      // clone vnode if it's reused because we are going to mutate it      if (vnode.el) {        vnode = cloneVNode(vnode)        if (rawVNode.shapeFlag & ShapeFlags.SUSPENSE) {          rawVNode.ssContent = vnode        }      }      // #1513 it's possible for the returned vnode to be cloned due to attr      // fallthrough or scopeId, so the vnode here may not be the final vnode      // that is mounted. Instead of caching it directly, we store the pending      // key and cache `instance.subTree` (the normalized vnode) in      // beforeMount/beforeUpdate hooks.      pendingCacheKey = key // 假如已经缓存了,则直接从缓存中获取组件实例给vnode,若还未缓存,则先辈行缓存      if (cachedVNode) {        // copy over mounted state        vnode.el = cachedVNode.el        vnode.component = cachedVNode.component // 实行transition        if (vnode.transition) {          // recursively update transition hooks on subTree          setTransitionHooks(vnode, vnode.transition!)        }//  设置shapeFlag标志位,为了制止实行组件mounted方法        // avoid vnode being mounted as fresh        vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE        // make this key the freshest   // 重新设置一下key包管最新        keys.delete(key)        keys.add(key)      } else {        keys.add(key)        // prune oldest entry  // 当超出max值时,扫除缓存        if (max && keys.size > parseInt(max as string, 10)) {          pruneCacheEntry(keys.values().next().value)        }      }      // avoid vnode being unmounted      vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE      current = vnode      return isSuspense(rawVNode.type) ? rawVNode : vnode    }  在上面的代码中,当缓存的个数凌驾max(默认值为10)的值时,就会扫除旧的数据,这其中就包罗<keep-alive>的缓存更新计谋,其依照了LRU(Least Rencently Used)算法。
1. LRU算法

  LRU算法根据数据的汗青访问记载来镌汰数据,其核心头脑是“假如数据最近被访问过,那么将来被访问的概率也更高”。使用这个思绪,我们可以对<keep-alive>中缓存的组件数据举行删除和更新,其算法的核心实现如下:

1.png
上面的代码中,主要使用map来存储缓存数据,使用map.keyIterator.next()来找到最久没有使用的key对应的数据,从而对缓存举行删除和更新。
2. 缓存VNode对象

  在render方法中,<keep-alive>并不是直接缓存的DOM节点,而是Vue中内置的VNode对象,VNode颠末render方法后,会被替换成真正的DOM内容。起首通过slots.default().children[0]获取第一个子组件,获取该组件的name。接下来会将这个name通过include与exclude属性举行匹配,若匹配不乐成(阐明不必要举行缓存),则不举行任何使用直接返回VNode。必要注意的是,<keep-alive>只会处置惩罚它的第一个子组件,以是假如给<keep-alive>设置多个子组件,是无法见效的。
  <keep-alive>尚有一个watch方法,用来监听include和exclude的改变,代码如下:
    // prune cache on include/exclude prop change    watch(      () => [props.include, props.exclude],      ([include, exclude]) => {  // 监听include和exclude,在被修改时对cache举行修正        include && pruneCache(name => matches(include, name))        exclude && pruneCache(name => !matches(exclude, name))      },      // prune post-render after `current` has been updated      { flush: 'post', deep: true }    )  这里的步伐逻辑是动态监听include和exclude的改变,从而动态地维护之前创建的缓存对象cache,着实就是对cache举行遍历,发现缓存的节点名称和新的规则没有匹配上时,就把这个缓存节点从缓存中摘除。下面来看pruneCache这个方法,代码如下:
    function pruneCache(filter?: (name: string) => boolean) {      cache.forEach((vnode, key) => {        const name = getComponentName(vnode.type as ConcreteComponent)        if (name && (!filter || !filter(name))) {          pruneCacheEntry(key)        }      })    }遍历cache中的全部项,假如不符合filter指定的规则,则会实行pruneCacheEntry,代码如下:
    function pruneCacheEntry(key: CacheKey) {      const cached = cache.get(key) as VNode      if (!current || !isSameVNodeType(cached, current)) {        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.        resetShapeFlag(current)      } // 烧毁VNode对应的组件实例      cache.delete(key)      keys.delete(key)    }上面的内容完成以后,当相应式触发时,<keep-alive>中的内容会改变,会调用<keep-alive>的render方法得到VNode,这里并没有用很深条理的diff去对比缓存前后的VNode,而是直接将旧节点置为null,用新节点举行替换,在patch方法中,直接掷中这里的逻辑,代码如下:
     // n1为缓存前的节点,n2为将要替换的节点     if (n1 && !isSameVNodeType(n1, n2)) {       anchor = getNextHostNode(n1)       // 卸载旧节点       unmount(n1, parentComponent, parentSuspense, true)       n1 = null     }然后通过setup方法中的sharedContext.activate和sharedContext.deactivate来举行内容的替换,其核心是move方法,代码如下:
    const move: MoveFn = () => {        // 替换DOM        ...        hostInsert(el!, container, anchor) // insertBefore修改DOM     }总结一下,<keep-alive>组件也是一个Vue组件,它通过自界说的render方法实现,而且使用了插槽。由于是直接使用VNode方式举行内容替换,不是直接存储DOM结构,因此不会实行组件内的生命周期方法,它通过include和exclude维护组件的cache对象,从而来处置惩罚缓存中的具体逻辑。
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-11-21 21:13, Processed in 0.166840 second(s), 35 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表