xuanweiH / Project-issue

记录项目遇到一些问题与封装
2 stars 0 forks source link

组件循环引用导致的组件加载失败问题 #12

Open xuanweiH opened 3 years ago

xuanweiH commented 3 years ago

记一次组件导入报错的问题

在开发需求的过程,需要在一个新建页面的保存时, 点击按钮跳转到对应的详情页. 但是在实习操作中发现点击这个按钮唤起组件时浏览器却报错了.

SelectTypeAndCatalogDialog.vue (这个页面是新建工单的页面
    <!-- 工单详情 -->
    <work-order-detail-dialog
      :visible.sync="workOrderDetail.visible"
      :id="workOrderDetail.id"
      @submit-success="handleClose"
    />
import WorkOrderDetailDialog from "@/components/WorkOrderDetailDialog";
调用这个工单详情页面的组件
等点击唤起组件时发现浏览器报错了

avatar 乍一看这个报错,我还以为是因为组件的驼峰没有写好 注意: vue在导入组件的时候, 组件名称一定要为驼峰形式, 组件在标签使用时用-隔开,很多时候这个报错都是因为命名造成的 不过很可惜 这里并不是因为命名, 在检查了好几篇之后用打印的方式尝试看看 在SelectTypeAndCatalogDialog.vue这个页面中打印 导入的WorkOrderDetailDialog发现确实为undefined 于是往上层组件开始寻找问题:

在allOrder.vue文件中(对于其他组件来说算是父级组件 全部工单列表)
      <!-- 添加工单 -->
      <select-type-and-catalog-dialog
        :visible.sync="addOrder.visible"
        @submit-success="initTableData"
      />

      <!-- 工单详情弹窗 -->
      <work-order-detail-dialog
        :id="workOrderDetail.id"
        :visible.sync="workOrderDetail.visible"
        @submit-success="fetchTableData"
      />
import WorkOrderDetailDialog from "@/components/WorkOrderDetailDialog";
import SelectTypeAndCatalogDialog from "@/components/SelectTypeAndCatalogDialog";
导入了这两个组件

发现在父组件中同时使用了这个两个组件 简单理解为 a(父) b(子) c(子) a调用了bc b调用了c, 发现WorkOrderDetailDialog其实在父组件和兄弟组件中都有调用 考虑可能是和引用的时候出错有关.仔细把两个组件都捋顺了一遍, 发现其实在WorkOrderDetailDialog组件中有调用一个panel组件 而在panel组件中又调用了SelectTypeAndCatalogDialog组件. 哦豁, 循环引用! 有问题! 整理整个组件调用的情况 得到如下图: avatar 实际上, WorkOrderDetailDialog作为工单详情页还有递归调用的情况, 经过排查其实和递归组件问题无关,所以这里暂时就不做展开.

那么相互引用会存在什么问题呢? 这里就涉及到es6的import模块相互引用的问题: 这里引用阮一峰老师的例子:

//a.js
console.log("before import b")
import {b} from "./b"
console.log("b is " + b)
export let a = b+1;

//b.js
console.log("before import a")
import {a} from "./a"
console.log("a is " + a)
export let b = a+1;

输出:

before import a
a is undefined
before import b
b is NAN

这里有一个有趣的现象就是第一句输出并不是before import b,也就是虽然import语句在后面,但确会更早执行,当执行import b时,加载并运行b.js,从而第一句输出是before import a。 然后就是当运行b.js时,发现又需要import a.js,此时不会再去加载a.js了,而是认为整个a.js模块是{},所以a的值就是undefined了

那么同理 对于我所遇到的问题: SelectTypeAndCatalogDialog中有调用WorkOrderDetailDialog WorkOrderDetailDialog中有调用SelectTypeAndCatalogDialog

从项目结构来看, WorkOrderDetailDialog就相当于是a.js中的a了 所以在SelectTypeAndCatalogDialog中我们就取不到WorkOrderDetailDialog了

遇到了问题, 那应该怎么解决呢: 目前想到了三种解决方案并都测试有效:

  1. 简单粗暴, 注册这个详情页的弹窗WorkOrderDetailDialog为全局组件, 这样就可以调用到了不存在import的导入undefined问题
  2. 在SelectTypeAndCatalogDialog里面不去套WorkOrderDetailDialog组件了, 只要用$emit去触发父级组件的值来动态展示, 这个方法的问题是如果有很多个页面都存在这种父级同时使用两个组件,要改很多地方,但是如果早期规划的时候考虑到不写这么多嵌套是可以考虑的.
  3. 不用import, 在局部组件components祖册的时候使用函数式WorkOrderDetailDialog: ()=> import(@/components/WorkOrderDetailDialog) 函数式的好处是,不像import那样在加载页面的时候就导入,而是在使用的时候才去加载这个组件,所以就没有了进入页面时的循环引入的问题.

好了这个问题的解决也让我加深了对循环引入的理解,受益良多 bye