原文地址:https://liubing.me/circular-references-between-components.html


事情是这样的,在近期重构一个之前写的乱七八糟的项目的时候,准备二次封装一下Element-Ui的NavMenu导航菜单组件,方便日常使用。
由于目前暂时还不考虑el-menu-item-group的问题,所以将NavMenu拆分成下面的几个小组件

menu-item

menu-item为原导航菜单组件的el-menu-item,大致代码如下:

<template>
  <div class="meun-item">
    <sub-menu v-if="menu.subMenu && menu.subMenu.length"
      :menu="menu" />
    <router-link v-else
      :to="menu.path">
      <el-menu-item :index="menu.menuName"
        :disabled="!!menu.disabled">
        <template slot="title">
          <i v-if="menu.icon"
            :class="['iconfont', menu.icon]"></i>
          <span class="menu-text">{ { menu.label }}</span>
        </template>
      </el-menu-item>
    </router-link>
  </div>
</template>

<script>
import SubMenu from './sub-menu.vue'
export default {
  props: {
    menu: Object
  },
  components: {
    SubMenu
  }
}
</script>

sub-menu

sub-menu为原导航菜单组件的el-submenu,大致代码如下:

<template>
  <el-submenu :index="menu.menuName">
    <template slot="title">
      <i v-if="menu.icon"
        :class="['iconfont', menu.icon]"></i>
      <span class="menu-text">{ { menu.label }}</span>
    </template>
    <menu-item v-for="(item, index) in menu.subMenu || []"
      :menu="item"
      :key="index" />
  </el-submenu>
</template>

<script>
import MenuItem from './menu-item.vue'
export default {
  name: 'SubMenu',
  props: {
    menu: Object
  },
  components: {
    MenuItem
  }
}
</script>

nav-menu

nav-menu为原导航菜单组件的el-menu

<template>
  <el-menu class="nav-menu">
    <template v-for="(menu, index) in menus">
      <sub-menu v-if="menu.subMenu && menu.subMenu.length"
        :menu="menu"
        :key="`sub-menu-${index}`" />
      <menu-item v-else
        :menu="menu"
        :key="`menu-item-${index}`" />
    </template>
  </el-menu>
</template>

<script>
import MenuItem from './menu-item.vue'
import SubMenu from './sub-menu.vue'
export default {
  props: {
    menus: Array
  },
  components: {
    MenuItem,
    SubMenu
  }
}

</script>

使用

<template>
    <nav-menu :menus="menus" />
</template>
<script>
import NavMenu from '@/components/nav-menu/nav-menu'
export default {
  components: {
    NavMenu
  },
  data () {
    return {
        menus: [
        {
          label: '菜单1',
          menuName: '1',
          path: ''
        },
        {
          label: '菜单2',
          menuName: '2',
          subMenu: [
            {
              label: '菜单2-1',
              menuName: '2-1',
              path: ''
            },
            {
              label: '菜单2-2',
              menuName: '2-2',
              subMenu: [
                {
                  label: '菜单2-2-1',
                  menuName: '2-2-1',
                  path: ''
                }
              ]
            }
          ]
        },
        {
          label: '菜单3',
          menuName: '3'
        }
      ]
    }
  }
}
</script>

出现报错情况

然后就会出现一个组件未注册的报错情况,然后仔细查代码,看不是否有menu-item拼错了,
发现没有,都是对的,每次刷新页面都会报这个错误,导致子集菜单不显示。
最后用排除大法,将menu-item中的sub-menu注释掉后该报错没有了,目测问题应该是出现在这儿,
但是sub-menu也没啥问题啊,百思不得其解,Google了半天才找到答案:链接

总结

归根结底还是组件之间的循环引用造成的问题,正如上面的链接所说的,
有两个组件称为 A 和 B(A、B就相当于这里的menu-itemsub-menu)。
模块系统发现它需要 A,但是首先 A 依赖 B,但是 B 又依赖 A,但是 A 又依赖 B,如此往复。
这变成了一个循环,不知道如何不经过其中一个组件而完全解析出另一个组件。
官方给的解决方法就是异步加载组件。
将上面的import加载的方式全部变成异步加载,如下

const MenuItem = () => import('./menu-item.vue')
const SubMenu = () => import('./sub-menu.vue')

测试下了果然报错就没了。(ps: 请忽略菜单及代码细节问题)