vue中父组件销毁时,保留子组件

刚结束了这样一个需求:使用tab/panel组件来展示各种类别的产品列表,每个产品列表的顶部是相同的推荐内容业务组件,需要随着列表页滑动而滑动。

由此产生了一个问题:推荐内容业务组件放在组件中什么位置?

  1. 每个panel下的列表页都放这么一个业务组件?

    将导致性能浪费(推荐内容的业务组件自身会查询多个接口),并且切换tab时,由于新建业务组件将导致页面闪烁(第一次切换到该tab);

  2. 不放在panel下的列表组件中?

    那么,业务组件不能随每个列表滑动而滑动。

又因为业务需要,切换tab时,必须重新查询产品列表,这里使用了多tab单panel。(并且由于panel上会有很多状态:各类组合筛选条件、排序等,使用单panel无需进行状态初始化)

每次切换tab时,重建新的panel(panel绑定了key属性,值为tab的名称),导致panel中的推荐内容业务组件也每次重建创建,出现页面闪烁。

思考出路

有没有一种方式,当panel组件重建(销毁)的时候,推荐内容的业务组件没有销毁,可以在重建的panel组件中继续使用。

使用keep-alive可行吗?

  1. 组件的销毁机制是:当父组件被销毁时,会先销毁子组件。
  2. 有一个例外,就是被<keep-alive>保护的组件(_vnode.data.keepAlive = true)不会被销毁。
  3. <keep-alive>组件自身销毁时,所有被其缓存的组件也一并被销毁。
  4. <keep-alive>组件只缓存它包裹的第一个子组件,不能直接缓存子组件下的子孙组件。
  5. 综合,<keep-alive>组件若是包裹整个panel组件,则相当于缓存了每个tab对应的panel组件(<keep-alive>是根据子组件的key值来缓存组件的),这与创建多panel效果是一致的。若是用<keep-alive>在panel组件内部包裹业务组件,则panel销毁时,<keep-alive>也会被销毁,导致业务组件被销毁。所以<keep-alive>无法满足。

使用Modal可行吗?

Modal组件(或传送门portal类似的组件)会与组件所在的环境组件创建父子联系,当父组件销毁时,Modal组件也会被销毁。(dom结构上与环境组件的dom没有父子关系,可以出现在任意位置,所以是传送门)。

自己造轮子

既然决定自己写这么一个组件,来实现父组件销毁而"子组件"不销毁。那么必须要做一些思考:

  1. 是否能够实现
  2. 先想好组件的名称
  3. 组件要做到方便好用
  4. 控制什么时机销毁(父组件销毁时,没有自动销毁)

能否实现

vue的机制就是父组件销毁前先销毁子组件(除被keep-alive的组件),这个无法绕过去。好在我们只需要dom结构上的父子关系,而非组件结构上的,所以是可行的。

这个组件必须是通过new VueComponent()的形式手动创建的组件实例,这个的组件实例是脱离原组件环境的,不会自动销毁。

组件名称

最怕起名了!!!父组件销毁时,子组件还能存活,暂且称它为小强吧,所以取名stubborn(顽固)。

设计使用方式

首先需要在合适的位置去创建这个推荐内容的业务组件。可以放在与panel平级(或上级)的位置。

xml 复制代码
<stubborn component="MyComponent" name="myComp" />
  • component - 指定需要被顽固化的组件
  • name - 给这个组件实例取个名字(可能一个会出现多个小强组件)

在panel中使用:

xml 复制代码
<stubborn show="myComp" />
  • show - 指定要展示的小强组件的名称

什么时机销毁<stubborn> 顽固化的组件

xml 复制代码
<stubborn component="MyComponent" name="myComp" />

在哪个组件中创建了顽固化的组件,就由当前组件去负责销毁。

代码实现

jsx 复制代码
import Vue from 'vue';
// 存放顽固化的组件
const components = {};
const nameToComponent = {};

export default {
  props: {
    name: String,
    component: String,
    show: String
  },
  data() {
    return {
      // 是否需要销毁
      needPrune: false
    };
  },
  render() {
    const { name, component: compName, show } = this;
    // panel中使用
    if (show) {
      // 仅返回一个标志位,用来插入顽固化组件的dom节点
      return <div />;
    }
    
    // 不返回内容(vnode),通过组件构造方法创建组件实例
    if (name && compName) {
      // 没有创建过该组件的构造方法
      if (!components[compName]) {
        // 取到组件的options
        const options = this.$vnode.context.$options.components[compName];
        // 创建组件的构造方法
        const ctor = Vue.extend(options);
        // 一个改造方法,可以创建多个要顽固化的组件
        components[compName] = { ctor, instances: {} };
      }
      const { ctor, instances } = components[compName];
      // 指定名称的实例不存在,需要创建
      if (!instances[name]) {
        instances[name] = new ctor();
        // 实例名称 -> 组件名: myComp -> MyComponent
        nameToComponent[name] = compName;
        // 在当前组件创建,则在当前组件销毁时销毁顽固化组件实例
        this.needPrune = true;
      }
    }
  },
  mounted() {
    // 负责展示的stubborn组件
    const { show } = this;
    if (!show) return;
    const compName = nameToComponent[show];
    const { instances } = components[compName];
    const inst = instances[show];
    // this.$el就是render中返回的标志位div
    const { $el } = this;
    // 首次挂载
    if (!inst._isMounted) {
      inst.$mount($el);
    } else {
      // 组件已挂载过,将挂载的dom移动过来展示
      $el.parentNode.insertBefore(inst.$el, $el);
      // 删除标志位
      $el.remove();
    }
  },
  beforeDestroy() {
    // 销毁顽固化组件实例
    if (this.needPrune) {
      const compName = nameToComponent[this.name];
      const { instances } = components[compName];
      const inst = instances[this.name];
      // 销毁组件实例
      inst.$destroy();
      // 手动删除dom节点
      inst.$el.remove();
      // 删除缓存
      delete instances[this.name];
      delete nameToComponent[this.name];
      if (Object.keys(instances).length === 0) {
        delete components[compName];
      }
    }
  }
};

由于需求紧急,还有很多实现不到位的地址,欢迎大家给出意见做进一步优化。

TODO

  1. 当前的实现方法不是特别灵活,不支持给需要顽固化的组件传入属性,或绑定事件(后期尝试一下通过propsData)。
相关推荐
Re.不晚5 分钟前
Web前端开发——HTML基础下
前端·javascript·html
几何心凉6 分钟前
如何处理前端表单验证,确保用户输入合法?
前端·css·前端框架
浪遏17 分钟前
面试官😏: 讲一下事件循环 ,顺便做道题🤪
前端·面试
Joeysoda37 分钟前
JavaEE进阶(2) Spring Web MVC: Session 和 Cookie
java·前端·网络·spring·java-ee
小周同学:1 小时前
npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚本。
前端·npm·node.js
浪遏1 小时前
面试官:字符串反转有多少种实现方式 ?| 一道题目检测你的基础
前端·面试
kjl5365661 小时前
前端题目类型
前端
IT、木易1 小时前
大白话CSS 优先级计算规则的详细推导与示例
前端·css·面试
小华同学ai1 小时前
2K star!三分钟搭建企业级后台系统,这款开源Java框架绝了!
后端·架构·github
B站计算机毕业设计超人2 小时前
计算机毕业设计SpringBoot+Vue.js民族婚纱预定系统(源码+文档+PPT+讲解)
java·vue.js·spring boot·后端·毕业设计·课程设计·毕设