简单实现tree组件,再也不怕递归组件啦

494 阅读1分钟

经常我们能看到这种结构的元素在Web页面中

output.gif

这种元素通常被称为Tree组件,像这种每个层级的结构都很类似,但是又要放在一个组件中去实现它,那么就需要用到递归

如何实现一个最简单tree组件

💡基本功能

  • 1️⃣ 展开折叠

  • 2️⃣ 有层次感

<template>
  <div>
    <div class="ul" v-for="item in treeData" :key="item.id">
      <div
        class="li"
        :style="{
          paddingLeft: level * 27 + 'px'
        }"
        @click.stop="clickNode(item)"
      >
        <span
          v-if="item.type === 'dir'"
          :class="['icon-triangle', item.isOpen ? 'rotate' : 'rotateFalse']"
        ></span>
        <span>{{ item.name }}</span>
      </div>
      <custom-tree v-if="item.isOpen" :data="item.children" :level="level + 1">
      </custom-tree>
    </div>
  </div>
</template>
<script>
import customTree from "./tree.vue";
export default {
  name: "custom-tree",
  components: { customTree },
  props: {
    data: {
      type: Array,
      default: () => []
    },
    level: {
      type: Number,
      default: 1
    }
  },
  data() {
    return {
      treeData: null
    };
  },
  created() {
    this.treeData = this.data;
    this.treeData.forEach((node) => {
      node.level = this.level;
      this.$set(node, "isOpen", true);
    });
  },
  methods: {
    clickNode(node) {
      node.isOpen = !node.isOpen;
    }
  }
};
</script>

进阶自定义节点

回忆一下,插槽一般是指父组件定义的内容插入到子组件中,而父组件定义的内容的数据是由父组件提供的

image.png

上述组件最终编译结果就是

<div class="father">
    <div class="child">
        <div>father</div>
    </div>
</div>

那么现在我想用到的是子组件的数据呢?

就是用到作用域插槽

<template>
  <div class="father">
      <child>
          <template v-slot="user">
              <div>{{user.people.name}}</div>
          </template>
      </child>
      <child>
          <template v-slot="{people}">
              <div>{{people.name}}</div>
          </template>
      </child>
  </div>
</template>
<script>
import child from './child.vue';
export default{
  components: {child},
}
<template>
  <div class="child">
      <slot :people="child"></slot>
  </div>
</template>
<script>
export default{
  data(){
      return {
          child: {
              name: '小明',
          }
      }
  }
}
</script>

看过往的例子,容易造成误解的是在子组件中去绑定的属性的名称应该用什么名称

网上的例子基本都是清一色的跟数据的名称一致比如说<slot :child="child"></slot>

实际上可以用任何的名称都可以,因为在父组件中v-slot拿到的数据是一个对象,对象的属性名称就是子组件绑定的属性名称

这个例子来说v-slot="user"的user实际上就是{"person": {"name":"小明"}}

如果在子组件中用<slot :child="child"></slot>那么user实际上是{"child":{"name":"小明"}}

image.png

基于作用域插槽,将上述的tree进行改造

<template>
  <div>
    <div class="ul" v-for="item in treeData" :key="item.id">
      <slot :node="item" :data="treeData">
        <div
          class="li"
          :style="{
            paddingLeft: level * 27 + 'px'
          }"
          @click.stop="clickNode(item)"
        >
          <span
            v-if="item.type === 'dir'"
            :class="['icon-triangle', item.isOpen ? 'rotate' : 'rotateFalse']"
          ></span>
          <span>{{ item.name }}</span>
        </div>
      </slot>
      <custom-tree v-show="item.isOpen" :data="item.children" :level="level + 1">
        <template v-slot="{ node, data }">
          <slot :node="node" :data="data">
            <div
              class="li"
              :style="{
                paddingLeft: level * 27 + 'px'
              }"
              @click.stop="clickNode(node)"
            >
              <span
                v-if="item.type === 'dir'"
                :class="[
                  'icon-triangle',
                  item.isOpen ? 'rotate' : 'rotateFalse'
                ]"
              ></span>
              <span>{{ item.name }}</span>
            </div>
          </slot>
        </template>
      </custom-tree>
    </div>
  </div>
</template

使用的方法,template中拿到子组件传过来的数据,根据这个数据去编写组件内容

    <custom-tree :data="data1">
      <template v-slot:default="{ node, data }">
        <div
          class="node"
          :style="{
            paddingLeft: node.level * 27 + 'px',
            backgroundColor: 'rgb(212,235,' + 80 * node.level + ')'
          }"
          @click="click(node)"
        >
        <span><input type="radio"></span>
          {{ node.name }}
        </div>
      </template>
    </custom-tree>

完整的代码看这里