最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Vue - The Good Parts: keep-alive

    正文概述 掘金(滴滴前端技术)   2021-06-28   579

    前言

    Vue 中有一个特别好用的组件 keep-alive 组件,我们在很多场景下,都是可以借助于这个组件来提升我们的产品体验,基本上0成本实现缓存效果。用的最多的场景就是和路由搭配,缓存不怎么更新的路由页面。

    那这么好用的功能,背后是怎么实现的,有哪些可以学习的,一起来分析下。

    正文分析

    What

    来自官网的介绍 cn.vuejs.org/v2/api/#kee…

    Vue - The Good Parts: keep-alive

    可以看到,他的使用,更多的是跟随者动态组件一起使用。最核心的就是缓存组件实例,以提升性能。在官网上也有一个tab切换的示例,就是他的一种使用场景 cn.vuejs.org/v2/guide/co…

    How

    既然是一个内置组件,那么它肯定也就是一个按照组件来定义的,Vue 中核心的实现在 github.com/vuejs/vue/b… 这里

    export default {
      // 组件名字
      name: 'keep-alive',
      // 抽象组件 这是一个没有对外暴露的组件声明属性
      // 作用的话,就是不渲染DOM 也不会出现在父组件的children中
      abstract: true,
     
      props: {
        include: patternTypes,
        exclude: patternTypes,
        max: [String, Number]
      },
     
      methods: {
        cacheVNode() {
          const { cache, keys, vnodeToCache, keyToCache } = this
          // 存在需要缓存的节点
          if (vnodeToCache) {
            const { tag, componentInstance, componentOptions } = vnodeToCache
            // 缓存到 cache 对象中
            cache[keyToCache] = {
              name: getComponentName(componentOptions),
              tag,
              componentInstance,
            }
            keys.push(keyToCache)
            // 判断是否超出了 max 最大缓存实例数
            // prune oldest entry
            if (this.max && keys.length > parseInt(this.max)) {
              // 如果超出了 就销毁超出的
              pruneCacheEntry(cache, keys[0], keys, this._vnode)
            }
            this.vnodeToCache = null
          }
        }
      },
     
      created () {
        // 初始化缓存对象
        this.cache = Object.create(null)
        this.keys = []
      },
     
      destroyed () {
        // 销毁的时候 所有实例全部销毁
        for (const key in this.cache) {
          pruneCacheEntry(this.cache, key, this.keys)
        }
      },
     
      mounted () {
        this.cacheVNode()
        // 如果这些有更新 一样需要再次 check 一遍所有的缓存实例 是否应该缓存
        this.$watch('include', val => {
          pruneCache(this, name => matches(val, name))
        })
        this.$watch('exclude', val => {
          pruneCache(this, name => !matches(val, name))
        })
      },
     
      updated () {
        // 更新钩子 再次缓存
        this.cacheVNode()
      },
     
      render () {
        // 重点 render 的实现
        // 可以获得组件内的默认内容 其实也就是 默认插槽内容
        const slot = this.$slots.default
        // 找到里边第一个组件节点
        const vnode: VNode = getFirstComponentChild(slot)
        // 通过 vnode 节点可以获得组件配置项
        const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
        if (componentOptions) {
          // check pattern
          const name: ?string = getComponentName(componentOptions)
          const { include, exclude } = this
          if (
            // not included
            (include && (!name || !matches(include, name))) ||
            // excluded
            (exclude && name && matches(exclude, name))
          ) {
            return vnode
          }
     
          const { cache, keys } = this
          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 (cache[key]) {
            // 之前缓存过了
            // 直接使用之前缓存的组件实例
            vnode.componentInstance = cache[key].componentInstance
            // 先删除掉这个 key 然后在push 保证这个 key 是新鲜的 超限check的时候 有用
            // make current key freshest
            remove(keys, key)
            keys.push(key)
          } else {
            // delay setting the cache until update
            // 设置好 vnodeToCache 当更新的时候 再去缓存 参考 updated 钩子中的逻辑
            this.vnodeToCache = vnode
            this.keyToCache = key
          }
     
          vnode.data.keepAlive = true
        }
        // 返回的就是内部的节点
        return vnode || (slot && slot[0])
      }
    }
    

    上边大概就是核心的一个流程:

    • 默认进来,取得当前渲染的 vnode
    • 然后进入 mounted 钩子,缓存上
    • 当有更新的时候,再次调用 render,设置 vnodeToCache
    • 到 updated 钩子,再次缓存上
    • 下次如果命中缓存,直接用现有实例即可

    你会发现要想很好的理解上述过程,要很好的理解 Vue 的生命周期,可以参考 cn.vuejs.org/v2/guide/in… 图如下:

    Vue - The Good Parts: keep-alive

    此外,还有很多关于 vnode 上的属性,如:componentOptions、key、data、componentInstance、tag 等,以及相配合的在 Vue 中是如何识别和运用这些属性的:如何不创新新的实例,如何触发新的生命周期钩子 activated deactivated 等,如果你对所有的逻辑细节比较感兴趣,可以参考黄老师的 ustbhuangyi.github.io/vue-analysi…

    Why

    我们可以理解为 Vue 为什么提供了内置组件 keep-alive?

    在前言的部分,我们也讲了在实际场景中,还是会遇到不少缓存组件的情况,在遇到路由场景的时候更甚。

    那 Vue 的一个理念就是对开发者很友好,框架做了很多事情,使得开发者可以专注于自身的逻辑开发工作,这也是为什么全球会有那么多开发者钟爱它的原因之一。那从这个点出发,因为有这么多的需求,所以 Vue 也就提供了这么好用的内置组件也就不难理解了。

    总结

    我们可以看出,keep-alive 的组件实现并不复杂,全部文件也就 150 行上下,但是功能却很强大,所有的功能参考 cn.vuejs.org/v2/api/#kee… 。那么从这个组件上,我们可以学到些什么东西,有什么可以借鉴的吗?

    Vue生命周期

    组件定义虽然不多,但是却是用到了 Vue 中绝大多数的生命周期钩子,且是我们也能经常使用到的:created、mounted、updated、destroyed。这也是我们写好组件的基石,正确理解并运用他们,知道他们的关系和过程,以及在对应的生命周期中适合做什么样的事情。

    额外提一点,在 created 生命周期钩子函数中,我们看到了如何给实例定义一些非响应式的对象,可以在 created 中直接 this.xxx = xxxx 的这种方式,而不是习惯性的把这些属性放到 data() 中去定义(会变为响应式对象,增加额外的开销),这种技巧值得我们学习和应用。

    Vue手写render

    在这里虽然十分简单,直接返回了默认插槽内容的节点,但是我们可以从这个点出发,在一些特殊场景,还是需要我们去手写 render 的,这部分也是需要我们去熟练运用的,详细的可以参考官网 cn.vuejs.org/v2/guide/re… 关于 render 函数的使用以及createElement、数据对象。

    keys设计

    keys 是一个数组,我们知道 keep-alive 还提供了一个能力:max 这个 prop,指定了最多可以缓存多少组件实例,一旦这个数字达到了,在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁掉。注意这里的关键:最久没有被访问的实例会被销毁掉

    这个是怎么做到的,排序吗?不用那么麻烦,通过上边的分析我们知道Vue中采用了一个很巧妙的做法:

    remove(keys, key)
    keys.push(key)
    

    简单来说,将 keys 中现在对应的 key 删除掉,然后把这个 key 再 push 到数组最后即可。

    通过这种方式,就可以保障了 keys 这个数组中最尾部的元素就是最新鲜的元素,最开始的元素就是最不新鲜的,在我们的场景中就可以对应为:最近被访问的实例。

    // prune oldest entry
    if (this.max && keys.length > parseInt(this.max)) {
      pruneCacheEntry(cache, keys[0], keys, this._vnode)
    }
    

    可以看到实际代码也是这样的,当超出 max 的时候,就是销毁的keys[0]对应的组件实例。

    内存泄漏

    在 destroyed 中,销毁了所有的实例,以释放在 cache 对象中缓存的对象们,这个就是对内存的管理,防止内存泄漏问题。这个是明面的内存销毁逻辑,大家只要注意了就不会遇到内存泄漏的问题了。

    但是这个不是绝对的,我们看一下最新的这个PR github.com/vuejs/vue/p… ,我们发现解决了两个内存泄漏的 issue,直观看起来是不应该存在内存泄漏的才对。

    这里就惊醒我们,要注意内存泄漏问题,他可能会是由于我们不经意之间写的代码所导致的。我们需要做到怎么利用开发者工具:

    • 如何知道内存泄漏了
    • 来定位&解决内存泄漏问题

    其他小Tips

    • 手工销毁组件实例,调用实例的 $destroy()
    • 访问 $slots.default 获得默认子节点们
    • vnode 上可以访问到 componentOptions以及 componentInstance,这俩还是很有用的

    滴滴前端技术团队的团队号已经上线,我们也同步了一定的招聘信息,我们也会持续增加更多职位,有兴趣的同学可以一起聊聊。


    起源地下载网 » Vue - The Good Parts: keep-alive

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元