什么是递归组件

  1. 如果某个组件通过组件名称引用它自己,这种情况就是递归组件;
  2. 实际开发中类似 Tree、Menu 这类组件,它们的节点往往包含子节点,子节点结构和父节点往往是相同的,这类组件的数据往往也是树形结构,这种都是使用递归组件的典型场景;
  3. 使用递归组件时,由于不能在组件内部导入它自己,所以设置组件 name 属性,用来查找组件定义,如果使用 SFC 则可以通过 SFC 文件名推断,组件内部通常也要有递归结束条件,比如 model.children 这样的判断;
  4. 查看生成渲染函数可知,递归组件查找时会传递一个布尔值给 resolveComponent,这样实际获取的组件就是当前组件本身;

为什么使用递归组件

  1. 对于一些有规律的 DOM 结构,如果再一遍遍的编写同样的代码,显然代码是比较繁琐和不科学的,而且自己的工作量会大大增加,那么有没有一种方法来解决这个问题呢?

  2. 答案是肯定的,可以通过 「递归」 方式来生成这个结构,当然在 Vue 模板中也是可以实现的,可以在 Vue 的组件中调用自己本身,这样就能达到目的;

在 Vue 中,组件可以递归的调用本身,但是有一些条件

  1. 该组件一定要有 name 属性;

  2. 要确保递归的调用有终止条件,防止内存溢出;

使用 jsx 语法的 render 实现

HTML
HTML
<template>
  <div style="display:flex;">
    <div style="flex:1">
      <ul>
        <my-tree :model="treeData" />
      </ul>
    </div>
  </div>
</template>
<script>
// 导入递归组件
import MyTree from './component/myTree.vue';
export default {
  name: 'App',
  components: {
    // 注册组件
    MyTree,
  },
  data() {
    return {
      treeData: [
        {
          title: 'Web全栈架构师',
          children: [
            {
              title: 'Java架构师',
            },
            {
              title: 'JS高级',
              children: [
                {
                  title: 'ES6',
                },
                {
                  title: '动效',
                },
              ],
            },
            {
              title: 'Web全栈',
              children: [
                {
                  title: 'Vue训练营',
                  children: [
                    {
                      title: '组件化',
                    },
                    {
                      title: '源码',
                    },
                    {
                      title: 'docker部署',
                    },
                  ],
                },
                {
                  title: 'React',
                  children: [
                    {
                      title: 'JSX',
                    },
                    {
                      title: '虚拟DOM',
                    },
                  ],
                },
                {
                  title: 'Node',
                },
              ],
            },
          ],
        },
      ],
    };
  },
};
</script>
<script>
export default {
  props: {
    model: {
      type: Object,
      required: true,
    },
  },
  render() {
    const toggle = menuItem => {
      return () => {
        menuItem.open = !menuItem.open;
      };
    };

    const renderChildred = data => {
      return data.map(child => {
        return child.children && child.children.length ? (
          <li>
            <div onclick={toggle(child)}>
              {child.title}
              {child.children && child.children.length 
                    ?(<span>{child.open ? '[-]' : '[+]'}</span>) 
                    : ('')}
            </div>
            {child.open ? <ul>{renderChildred(child.children)}</ul> : ''}
          </li>
        ) : (
          <li>
            <div>{child.title}</div>
          </li>
        );
      });
    };
    return renderChildred(this.model);
  },
};
</script>

使用模板实现

HTML
HTML
<template>
  <div style="display:flex;">
    <div style="flex:1">
      <ul v-for="treeItem in treeData" :key="treeItem">
        <item-view :model="treeItem" />
      </ul>
    </div>
  </div>
</template>
<script>
// 导入递归组件
import ItemView from './component/item.vue';
export default {
  name: 'App',
  components: {
    // 注册组件
    ItemView,
  },
  data() {
    return {
      treeData: [
        {
          title: 'Web全栈架构师',
          children: [
            {
              title: 'Java架构师',
            },
            {
              title: 'JS高级',
              children: [
                {
                  title: 'ES6',
                },
                {
                  title: '动效',
                },
              ],
            },
            {
              title: 'Web全栈',
              children: [
                {
                  title: 'Vue训练营',
                  children: [
                    {
                      title: '组件化',
                    },
                    {
                      title: '源码',
                    },
                    {
                      title: 'docker部署',
                    },
                  ],
                },
                {
                  title: 'React',
                  children: [
                    {
                      title: 'JSX',
                    },
                    {
                      title: '虚拟DOM',
                    },
                  ],
                },
                {
                  title: 'Node',
                },
              ],
            },
          ],
        },
      ],
    };
  },
};
</script>
<template>
  <li>
    <!-- 点击折叠展开 -->
    <div @click="toggle">
      <!-- 显示内容 -->
      {{ model.title }}
      <!-- 显示折叠展开的图标,如果没有下级目录的话,则不显示 -->
      <span v-if="isFolder">[{{ open ? "-" : "+" }}]</span>
    </div>
    <!-- 控制是否显示下级目录 -->
    <ul v-show="open" v-if="isFolder">
      <!-- 重点代码,调用自身,实现递归,绑定数据 -->
      <item-view v-for="model in model.children" :model="model" :key="model.title" />
    </ul>
  </li>
</template>
<script>
export default {
  name: 'ItemView',
  // 如果想使用此组件,则需要传递的数据
  props: {
    model: {
      type: Object,
      required: true,
    },
  },
  data() {
    return {
      // 默认不显示下级目录
      open: false,
    };
  },
  computed: {
    // 控制是否有下级目录和显示下级目录
    isFolder() {
      return this.model.children && this.model.children.length;
    },
  },
  methods: {
    // 点击折叠展开的方法
    toggle() {
      if (this.isFolder) {
        this.open = !this.open;
      }
    },
  },
};
</script>

效果展示

打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

中午好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. 什么是递归组件
  2. 2. 为什么使用递归组件
  3. 3. 在 Vue 中,组件可以递归的调用本身,但是有一些条件
  4. 4. 使用 jsx 语法的 render 实现
  5. 5. 使用模板实现
  6. 6. 效果展示