vue递归组件实际运用

工作忙完之后,现在就有时间去总结最近遇到的难题跟使用的扫技巧。接下来的技巧就是vue的递归组件。那么什么是递归组件呢?其实很简单,就是组件自己调用自己,现在我们从一个需求开始使用递归组件。(没错,又是Vue2实现的,真tm苦逼啊~)

需求

接下来我们需要实现一个树形结构的评论列表展示。具体效果如下:

实现

组件的内容比较简单,主要是为了展示如何使用递归组件。这是ChatItem的组件:

js 复制代码
<template>
  <div class="tree-item">
    <div class="container">
      <div class="content">{{ treeData.content }}</div>
      <div class="reply-btn">回复</div>
    </div>
    <Editor v-if="treeData.show" />
  </div>
</template>

<script>
import Editor from "./Editor.vue";
export default {
  components: {
    Editor,
  },
  props: {
    index: {
      type: Number,
      default: 0,
    },
    treeData: {
      type: Object,
      default() {
        return {};
      },
    },
  },
  methods: {
  },
};
</script>

<style scoped>
.container {
  display: flex;
}

.reply-btn {
  color: hotpink;
  cursor: pointer;
  margin-left: 10px;
}
</style>

这是父组件的代码:

javascript 复制代码
<template>
  <div id="app">
    <template v-for="(tree, index) in treeData">
      <chat-item
        :treeData="tree"
        :index="index"
        :key="tree.id"
      />
    </template>
  </div>
</template>

<script>
import chatItem from "./components/chatItem.vue";
export default {
  components: {
    chatItem,
  },
  data() {
    return {
      treeData: [
        {
          id: 1,
          content: "这是内容1",
          show: false,
          children: [
            {
              id: 11,
              content: "这是内容11",
              show: false,
              children: [
                {
                  id: 111,
                  content: "这是内容111",
                  show: false,
                  children: [],
                },
              ],
            },
          ],
        },
        {
          id: 2,
          content: "这是内容2",
          show: false,
          children: [
            {
              id: 21,
              content: "这是内容21",
              show: false,
              children: [],
            },
          ],
        },
        {
          id: 3,
          content: "这是内容3",
          show: false,
          children: [
            {
              id: 32,
              content: "这是内容32",
              show: false,
              children: [],
            },
            {
              id: 33,
              content: "这是内容33",
              show: false,
              children: [
                {
                  id: 332,
                  content: "这是内容332",
                  show: false,
                  children: [],
                },
              ],
            },
          ],
        },
        {
          id: 4,
          content: "这是内容4",
          show: false,
          children: [],
        },
      ],
      trigger: [],
    };
  },
  methods: {},
};
</script>

<style></style>

那么如何实现组件自己调用自己呢?其实很简单,看来面代码:

diff 复制代码
<template>
  <div class="tree-item">
    <div class="container">
      <div class="content">{{ treeData.content }}</div>
      <div class="reply-btn">回复</div>
    </div>
    <Editor v-if="treeData.show" />
+  <div
+    class="sub-container"
+    v-if="treeData.children && treeData.children.length > 0"
+  >
+   <template v-for="(sub, index) in treeData.children">
+     <chat-item
+       :index="index"
+       :key="sub.id"
+       :tree-data="sub"
+     >
+     </chat-item>
+       </template>
+     </div>
  </div>
</template>

<script>
import Editor from "./Editor.vue";
export default {
+  name: "ChatItem",
  components: {
    Editor,
  },
  props: {
    index: {
      type: Number,
      default: 0,
    },
    treeData: {
      type: Object,
      default() {
        return {};
      },
    },
  },
  data() {
    return {
      level: 0,
    };
  },
  methods: {},
};
</script>

<style scoped>
.container {
  display: flex;
}

.reply-btn {
  color: hotpink;
  cursor: pointer;
  margin-left: 10px;
}

+ .sub-container {
+   margin-left: 20px;
+ }
</style>

这样就完成了组件递归的使用,但是又会遇到一个问题,这种数据格式下,我怎么才能精确的获取到数据呢?对我这个经常用ElementUI库组件的人来说,树形组件的index一般都是一个数组,然后就是按照一级一级的往下,放具体的位置index,实现这个东西也不能,可以看看下面:

js 复制代码
<template>
  <div class="tree-item">
    <div class="container">
      <div class="content">{{ treeData.content }}</div>
      <div class="reply-btn" @click="showReply">回复</div>
    </div>
    <Editor v-if="treeData.show" />

    <div
      class="sub-container"
      v-if="treeData.children && treeData.children.length > 0"
    >
      <template v-for="(sub, index) in treeData.children">
        <chat-item
          :index="index"
          :key="sub.id"
          :tree-data="sub"
          @node-click="handleNodeClick"
        >
        </chat-item>
      </template>
    </div>
  </div>
</template>

<script>
import Editor from "./Editor.vue";
export default {
  name: "ChatItem",
  components: {
    Editor,
  },
  props: {
    index: {
      type: Number,
      default: 0,
    },
    treeData: {
      type: Object,
      default() {
        return {};
      },
    },
  },
  data() {
    return {
      level: 0,
    };
  },
  methods: {
    showReply() {
      this.$emit("node-click", {
        node: this.treeData,
        index: [this.index],
        show: !this.treeData.show,
      });
    },
    handleNodeClick({ node, index, show }) {
      this.$emit("node-click", {
        node,
        index: [this.index, ...index],
        show,
      });
    },
  },
};
</script>

<style scoped>
.container {
  display: flex;
}

.reply-btn {
  color: hotpink;
  cursor: pointer;
  margin-left: 10px;
}

.sub-container {
  margin-left: 20px;
}
</style>

说起来会比较绕,实际上就是,点击回复的时候,我们emit一个方法,把当前的数据,index,以及show导出。那么对于chatItem的上级来说,我们就需要用方法去接受它传出来的值,拿到值的时候,其实只需要将当前上级的index,放到index数组的第一位即可,紧接着就继续用emit出这个方法。这样就可以实现层层emit,最终获取到数据的index具体数组。那么外层既可以拿到这个具体是数据,看看下面的gif:

获取到具体的位置,又能拿到当前点击的数据,接下来要实现的就是这个回复框了。需求比较简单,就是我只希望出现一个回复框,点击的时候其他的回复框需要隐藏。

我第一个想到的就是排插 ,什么意思呢?就是我先将所有的节点,遍历一遍,全部设置为false不显示,然后再将目标数据设置为true 。这个方法最大的问题就是这个遍历,层级较深时这遍历起来的时间复杂度就比较恐怖了。

那么另外一个方法就是:我直接记住你每次点击的数据,然后在下一个点击的时候,将你这个数据的值设置为false,然后将当前数据的值设置为ture,这样就不需要去遍历数据了 。这就是为什么,我需要获取当前数据的index位置,这样就可以方便我去操作具体的数据。看下面父组件代码:

js 复制代码
<template>
  <div id="app">
    <template v-for="(tree, index) in treeData">
      <chat-item
        :treeData="tree"
        :index="index"
        :key="tree.id"
        @node-click="handleNodeClick"
      />
    </template>
  </div>
</template>

<script>
import chatItem from "./components/chatItem.vue";
export default {
  name: "App",
  components: {
    chatItem,
  },
  data() {
    return {
      treeData: [
        {
          id: 1,
          content: "这是内容1",
          show: false,
          children: [
            {
              id: 11,
              content: "这是内容11",
              show: false,
              children: [
                {
                  id: 111,
                  content: "这是内容111",
                  show: false,
                  children: [],
                },
              ],
            },
          ],
        },
        {
          id: 2,
          content: "这是内容2",
          show: false,
          children: [
            {
              id: 21,
              content: "这是内容21",
              show: false,
              children: [],
            },
          ],
        },
        {
          id: 3,
          content: "这是内容3",
          show: false,
          children: [
            {
              id: 32,
              content: "这是内容32",
              show: false,
              children: [],
            },
            {
              id: 33,
              content: "这是内容33",
              show: false,
              children: [
                {
                  id: 332,
                  content: "这是内容332",
                  show: false,
                  children: [],
                },
              ],
            },
          ],
        },
        {
          id: 4,
          content: "这是内容4",
          show: false,
          children: [],
        },
      ],
      trigger: [],
    };
  },
  methods: {
    handleNodeClick({ node, index, show }) {
      if (show) {
        if (this.trigger.length) {
        // 没有数据的时候,说明是第一次点击
          const targetIndex = this.trigger.shift();
          const target = this.getTargetNode(targetIndex);
          target.show = false;
        }
        node.show = true;
        this.trigger.push(index);
      } else {
      // false时候说明这个时候点击的是同一个
        node.show = false;
        this.trigger.shift();
      }
    },
    getTargetNode(index) {
    // 除了第一层,其他都是children下面的数据
      let parent = this.treeData[index[0]];
      let i = 1;
      if (index.length === 1) {
        return parent;
      } else {
        while (i < index.length) {
          parent = parent.children[index[i]];
          i++;
        }
        return parent;
      }
    },
  },
};
</script>

<style></style>

这样方法挺不错,但是你可能会说,你为什么不直接将node节点放到数据呢?这样你连这个index数据都不需要了,而且也有id可以作为两次点击是否一样的判断。确实,这样会更简单,但是嘛写这个不就是为了锻炼一下自己的能力的,都搞那么简单就没必要了是不是。下面是完整的ChatItem的代码:

js 复制代码
<template>
  <div class="tree-item">
    <div class="container">
      <div class="content">{{ treeData.content }}</div>
      <div class="reply-btn" @click="showReply">回复</div>
    </div>
    <Editor v-if="treeData.show" />

    <div
      class="sub-container"
      v-if="treeData.children && treeData.children.length > 0"
    >
      <template v-for="(sub, index) in treeData.children">
        <chat-item
          :index="index"
          :key="sub.id"
          :tree-data="sub"
          @node-click="handleNodeClick"
        >
        </chat-item>
      </template>
    </div>
  </div>
</template>

<script>
import Editor from "./Editor.vue";
export default {
  name: "ChatItem",
  components: {
    Editor,
  },
  props: {
    index: {
      type: Number,
      default: 0,
    },
    treeData: {
      type: Object,
      default() {
        return {};
      },
    },
  },
  data() {
    return {
      level: 0,
    };
  },
  methods: {
    showReply() {
      this.$emit("node-click", {
        node: this.treeData,
        index: [this.index],
        show: !this.treeData.show,
      });
    },
    handleNodeClick({ node, index, show }) {
      this.$emit("node-click", {
        node,
        index: [this.index, ...index],
        show,
      });
    },
  },
};
</script>

<style scoped>
.container {
  display: flex;
}

.reply-btn {
  color: hotpink;
  cursor: pointer;
  margin-left: 10px;
}

.sub-container {
  margin-left: 20px;
}
</style>

主要是怕上面的代码可能会删除多了,怕大家运行起来不行。

最后的吹牛

终于是又更新了文章,如果大家觉得受用的话,可以给我点个赞收藏啥的,已经是3级很久了,希望大伙给我升升,拜托了。文章有什么疑问或者问题欢迎大家在评论区留言,谢谢了(砰砰砰)。

相关推荐
anyup_前端梦工厂12 分钟前
初始 ShellJS:一个 Node.js 命令行工具集合
前端·javascript·node.js
5hand17 分钟前
Element-ui的使用教程 基于HBuilder X
前端·javascript·vue.js·elementui
GDAL34 分钟前
vue3入门教程:ref能否完全替代reactive?
前端·javascript·vue.js
z千鑫1 小时前
【前端】详解前端三大主流框架:React、Vue与Angular的比较与选择
前端·vue.js·react.js
小马哥编程3 小时前
Function.prototype和Object.prototype 的区别
javascript
苹果醋33 小时前
React系列(八)——React进阶知识点拓展
运维·vue.js·spring boot·nginx·课程设计
王小王和他的小伙伴3 小时前
解决 vue3 中 echarts图表在el-dialog中显示问题
javascript·vue.js·echarts
学前端的小朱3 小时前
处理字体图标、js、html及其他资源
开发语言·javascript·webpack·html·打包工具
outstanding木槿3 小时前
react+antd的Table组件编辑单元格
前端·javascript·react.js·前端框架
小k_不小4 小时前
C++面试八股文:指针与引用的区别
c++·面试