前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >vue3 迫不得已我硬着头皮查看了keepalive的源代码,解决了线上的问题

vue3 迫不得已我硬着头皮查看了keepalive的源代码,解决了线上的问题

作者头像
aehyok
发布2022-06-01 09:14:07
发布2022-06-01 09:14:07
65500
代码可运行
举报
文章被收录于专栏:技术博客技术博客
运行总次数:0
代码可运行
  • 1、通过本文可以了解到vue3 keepalive功能
  • 2、通过本文可以了解到vue3 keepalive使用场景
  • 3、通过本文可以学习到vue3 keepalive真实的使用过程
  • 4、通过本文可以学习vue3 keepalive源码调试
  • 5、通过本文可以学习到vue3 keepalive源码的精简分析

1、keepalive功能

  • keepalive是vue3中的一个全局组件
  • keepalive 本身不会渲染出来,也不会出现在dom节点当中,但是它会被渲染为vnode,通过vnode可以跟踪到keepalive中的cache和keys,当然也是在开发环境才可以,build打包以后没有暴露到vnode中(这个还要再确认一下)
  • keepalive 最重要的功能就是缓存组件
  • keepalive 通过LRU缓存淘汰策略来更新组件缓存,可以更有效的利用内存,防止内存溢出,源代码中的最大缓存数max为10,也就是10个组件之后,就开始淘汰最先被缓存的组件了

2、keepalive使用场景

  • 这里先假设一个场景: A页面是首页=> B页面列表页面(需要缓存的页面)===> C 详情页 由C详情页到到B页面的时候,要返回到B的缓存页面,包括页面的基础数据和列表的滚动条位置信息 如果由B页面返回到A页面,则需要将B的缓存页清空
  • 上述另外一个场景:进入页面直接缓存,然后就结束了,这个比较简单本文就不讨论了

3、在项目中的使用过程

4、vue3 keepalive源码调试

5、vue3 keealive源码粗浅分析

通过查看vue3 KeepAlive.ts源码,源码路径:

代码语言:javascript
代码运行次数:0
复制
    // 在setup初始化中,先获取keepalive实例
    // getCurrentInstance() 可以获取当前组件的实例
    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

    /// SSR 判断,暂时可以忽略掉即可。
    if (__SSR__ && !sharedContext.renderer) {
        return () => {
            const children = slots.default && slots.default()
            return children && children.length === 1 ? children[0] : children
        }
    }

    // 通过Map存储缓存vnode,
    // 通过Set存储缓存的key(在外面设置的key,或者vnode的type)
    const cache: Cache = new Map()
    const keys: Keys = new Set()
    let current: VNode | null = null

    if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
    ;(instance as any).__v_cache = cache
    }

    const parentSuspense = instance.suspense

    const {
    renderer: {
        p: patch,
        m: move,
        um: _unmount,
        o: { createElement }
    }
    } = sharedContext

    // 创建了隐藏容器
    const storageContainer = createElement('div')

    // 在实例上注册两个钩子函数 activate,  deactivate

    sharedContext.activate = (vnode, container, anchor, isSVG, optimized) => {
        const instance = vnode.component!
        move(vnode, container, anchor, MoveType.ENTER, parentSuspense)
        // in case props have changed
        patch(
            instance.vnode,
            vnode,
            container,
            anchor,
            instance,
            parentSuspense,
            isSVG,
            vnode.slotScopeIds,
            optimized
        )
        queuePostRenderEffect(() => {
            instance.isDeactivated = false
            if (instance.a) {
            invokeArrayFns(instance.a)
            }
            const vnodeHook = vnode.props && vnode.props.onVnodeMounted
            if (vnodeHook) {
            invokeVNodeHook(vnodeHook, instance.parent, vnode)
            }
        }, parentSuspense)

        if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
            // Update components tree
            devtoolsComponentAdded(instance)
        }
    }

    sharedContext.deactivate = (vnode: VNode) => {
        const instance = vnode.component!
        move(vnode, storageContainer, null, MoveType.LEAVE, parentSuspense)
        queuePostRenderEffect(() => {
            if (instance.da) {
            invokeArrayFns(instance.da)
            }
            const vnodeHook = vnode.props && vnode.props.onVnodeUnmounted
            if (vnodeHook) {
            invokeVNodeHook(vnodeHook, instance.parent, vnode)
            }
            instance.isDeactivated = true
        }, parentSuspense)

        if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
            // Update components tree
            devtoolsComponentAdded(instance)
        }
    }


    // 组件卸载
    function unmount(vnode: VNode) {
        // reset the shapeFlag so it can be properly unmounted
        resetShapeFlag(vnode)
        _unmount(vnode, instance, parentSuspense, true)
    }

    // 定义 include和exclude变化时,对缓存进行动态处理
    function pruneCache(filter?: (name: string) => boolean) {
        cache.forEach((vnode, key) => {
            const name = getComponentName(vnode.type as ConcreteComponent)
            if (name && (!filter || !filter(name))) {
            pruneCacheEntry(key)
            }
        })
    }

    function pruneCacheEntry(key: CacheKey) {
        const cached = cache.get(key) as VNode
        if (!current || cached.type !== current.type) {
            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)
        }
        cache.delete(key)
        keys.delete(key)
    }


    // 可以发现通过include 可以配置被显示的组件,
    // 当然也可以设置exclude来配置不被显示的组件,
    // 组件切换时随时控制缓存
    watch(
    () => [props.include, props.exclude],
    ([include, exclude]) => {
        include && pruneCache(name => matches(include, name))
        exclude && pruneCache(name => !matches(exclude, name))
    },
    // prune post-render after `current` has been updated
    { flush: 'post', deep: true }
    )

    // 定义当前组件Key
    // cache sub tree after render
        let pendingCacheKey: CacheKey | null = null

        // 这是一个重要的方法,设置缓存
        const cacheSubtree = () => {
        // fix #1621, the pendingCacheKey could be 0
        if (pendingCacheKey != null) {
            cache.set(pendingCacheKey, getInnerChild(instance.subTree))
        }
        }
        onMounted(cacheSubtree)
        onUpdated(cacheSubtree)

        // 组件卸载的时候,对缓存列表进行循环判断处理
        onBeforeUnmount(() => {
            cache.forEach(cached => {
                const { subTree, suspense } = instance
                const vnode = getInnerChild(subTree)
                if (cached.type === vnode.type) {
                // current instance will be unmounted as part of keep-alive's unmount
                resetShapeFlag(vnode)
                // but invoke its deactivated hook here
                const da = vnode.component!.da
                da && queuePostRenderEffect(da, suspense)
                return
                }
                unmount(cached)
            })
        })

    // 同时在keepAlive组件setup生命周期中,return () => {} 渲染的时候,对组件进行判断逻辑处理,同样对include和exclude判断渲染。
    
    // 判断keepalive组件中的子组件,如果大于1个的话,直接警告处理了
    // 另外如果渲染的不是虚拟dom(vNode),则直接返回渲染即可。

    return () => {
        // eslint-disable-next-line no-debugger
        console.log(props.include, 'watch-include')
        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
        }

        // 接下来处理时Vnode虚拟dom的情况,先获取vnode
        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
        // 获取组件名称
        const name = getComponentName(
            isAsyncWrapper(vnode)
            ? (vnode.type as ComponentOptions).__asyncResolved || {}
            : comp
        )

        //这个算是最熟悉的通过props传递进行的参数,进行解构
        const { include, exclude, max } = props

        // include判断 组件名称如果没有设置, 或者组件名称不在include中,
        // exclude判断 组件名称有了,或者匹配了
        // 对以上两种情况都不进行缓存处理,直接返回当前vnode虚拟dom即可。
        if (
            (include && (!name || !matches(include, name))) ||
            (exclude && name && matches(exclude, name))
        ) {
            current = vnode
            return rawVNode
        }

        // 接下来开始处理有缓存或者要缓存的了

        // 先获取一下vnode的key设置,然后看看cache缓存中是否存在
        const key = vnode.key == null ? comp : vnode.key
        const cachedVNode = cache.get(key)

        // 这一段可以忽略了,好像时ssContent相关,暂时不管了,没看明白??
        // 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
            }
        }

        // 上面判断了,如果没有设置key,则使用vNode的type作为key值
        pendingCacheKey = key

        //判断上面缓存中是否存在vNode

        // if 存在的话,就将缓存中的vnode复制给当前的vnode
        // 同时还判断了组件是否为过渡组件 transition,如果是的话 需要注册过渡组件的钩子
        // 同时先删除key,然后再重新添加key

        // else 不存在的话,就添加到缓存即可
        // 并且要判断一下max最大缓存的数量是否超过了,超过了,则通过淘汰LPR算法,删除最旧的一个缓存
        // 最后又判断了一下是否为Suspense。也是vue3新增的高阶组件。
        if (cachedVNode) {
            // copy over mounted state
            vnode.el = cachedVNode.el
            vnode.component = cachedVNode.component
            if (vnode.transition) {
            // recursively update transition hooks on subTree
            setTransitionHooks(vnode, vnode.transition!)
            }
            // avoid vnode being mounted as fresh
            vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE
            // make this key the freshest
            keys.delete(key)
            keys.add(key)
        } else {
            keys.add(key)
            // prune oldest entry
            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

6、总结

通过这次查看vue3 keepalive源码发现,其实也没那么难,当然这次查看源代码也只是粗略查看,并没有看的那么细,主要还是先解决问题。动动手调试一下,有时候真的就是不逼一下自己都不知道自己有多么的优秀。原来我也能稍微看看源代码了。以后有空可以多看看vue3源代码,学习一下vue3的精髓。了解vue3更为细节的一些知识点。

https://github.com/aehyok/vue-qiankun 本文涉及到的代码后续会整理到该代码仓库中

https://github.com/aehyok/2022 最后自己每天工作中的笔记记录仓库,主要以文章链接和问题处理方案为主。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-05-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、keepalive功能
  • 2、keepalive使用场景
  • 3、在项目中的使用过程
  • 4、vue3 keepalive源码调试
  • 5、vue3 keealive源码粗浅分析
  • 6、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档