vue3 tree树型组件封装思路以及示例

首先根据产品需求,确定该组件的功能有哪些!!!

一、结合功能需求我想到的是

  • 1、data传入树型数据
  • 2、提供自定义节点插槽(类似于el-tree的default插槽)
  • 3、右击方法
  • 4、单击收缩/展开当前节点的子节点

二、定义数据结构

[
	{
		children:[],
		name:"菜单1",
		open:false, // 为了方便我放在一起了
		...
		//其他的属性随便你了
	}
]

三、目录结构

// components/TreeC
-- TreeC
------index.vue
------Tree.vue

使用示例

<TreeC:data="fileTree" @right-click="divRightClick" @node-click="nodeClick">
    <template #default="{ data }">
      <!-- // 这里我只写了名字,其他的样式排版随便你了 -->
      	<div>
			{{ data.name }}
		</div>
    </template>
 </TreeC>

下面我们先处理插槽的问题。
使用示例中已经开始使用插槽了,这个时候我需要在tree中使用插槽,但是我的结构是这样的

//TreeC/index.vue
<template>
  <div class="overflow-auto">
    <div class="pb-4">
      <div class="px-16px">根目录</div>
      <Tree :data="treeData" class="pr-16px"></Tree>
    </div>
  </div>
</template>

//我的tree组件应该是这样的
// TreeC/Tree.vue
<template>
  <div class=" pl-30px">
    <div v-for="d in data" :key="d.name" class=" pt-22px">
      <div class="flex items-center" :style="{ '--item-left': itemLeft }">
        <!-- 标题 -->
        <div class="flex-1 z-2" @contextmenu.stop="($event) => rightClick($event, d)" @click.stop="changeOpenStatus(d)">
          {{ d.name }}
        </div>
        </template>
      </div>
      <Tree v-if="d.children && d.children.length > 0 && d.open" :data="d.children" :parent-data="d"></Tree>
    </div>
  </div>
</template>
// 这里我已经给节点添加了右击(rightClick),单机(changeOpenStatus)

看到这里我是如何把插槽传递给Tree组件呢?
因为在index.vue我是不需要使用这个default插槽的,所以我想到了provide/inject
于是:

// TreeC/index.vue
<script lang="ts" setup>
import Tree from "./Tree.vue";
import { PROJECT_PROVIDE_SYMBOL } from "@/static/symbol";
const slots = getCurrentInstance()?.slots;
provide(PROJECT_PROVIDE_SYMBOL, { slots });
</script>

//TreeC/Tree.vue
const parentInject = inject<{
  slots: {
    [key: string]: VNode;
  };
}>(PROJECT_PROVIDE_SYMBOL);

这个时候不管Tree在实际循环中嵌套多少层树,我都能拿到index.vue传过来的default插槽!

走到这里我该如何渲染呢?这个时候我发现我的组件文件需要扩充,于是我新建了个NodeTitle.vue

// components/TreeC
-- TreeC
------index.vue
------Tree.vue
------NodeTitle.vue

修改后:

// TreeC/Tree.vue
<template>
  <div class="pl-30px">
    <div v-for="d in data" :key="d.name" class="pt-22px">
      <div class="flex items-center" :style="{ '--item-left': itemLeft }">
        <!-- 标题 -->
        <div class=" flex-1 z-2" @contextmenu.stop="($event) => rightClick($event, d)" @click.stop="changeOpenStatus(d)">
          <NodeTitle v-if="parentInject && parentInject.slots.default" :data="d"></NodeTitle>
          {{ d.name }}
        </div>
        </template>
      </div>
      <Tree v-if="d.children && d.children.length > 0 && d.open" :data="d.children" :parent-data="d"></Tree>
    </div>
  </div>
</template>

//TreeC/NodeTitle.vue
<script lang="ts">
import { defineComponent, inject, renderSlot, VNode } from "vue";

import { TreeFile } from "./type";

import { PROJECT_PROVIDE_SYMBOL } from "@/static/symbol";

export default defineComponent({
  name: "TreeNodeTitle",
  props: {
    data: {
      required: true,
      type: Object as () => TreeFile
    }
  },
  setup(props) {
    const parentInject = inject<{
      slots: {
        [key: string]: VNode;
      };
    }>(PROJECT_PROVIDE_SYMBOL);
    return () => {
      return renderSlot(parentInject?.slots as unknown as any, "default", { data: props.data });
    };
  }
});
</script>

这样插槽就完成了

四、右击、单击方法实现
同样使用了provide/inject

// TreeC/index.vue
const emits = defineEmits(["right-click", "node-click"]);

function rightNodeClick(event: Event, nodeData: ProjectTree, node: TreeNode) {
  emits("right-click", event, nodeData, node);
}

function nodeClick(nodeData: ProjectTree, node: any) {
  emits("node-click", nodeData, node);
}

provide(PROJECT_PROVIDE_SYMBOL, { rightNodeClick, nodeClick, slots });

// TreeC/Tree.vue
const parentInject = inject<{
  rightNodeClick: (event: Event, data: TreeFile, node?: TreeNode) => void;
  nodeClick: (data: TreeFile, node?: TreeNode) => void;
  slots: {
    [key: string]: VNode;
  };
}>(PROJECT_PROVIDE_SYMBOL);

function rightClick(event: Event, nodeData: TreeFile) {
  parentInject && parentInject.rightNodeClick(event, nodeData, { level: props.lavel!, parent: props.parentData, data: nodeData });
}

function changeOpenStatus(e: TreeFile) {
  e.open = !e.open;
  parentInject && parentInject.nodeClick(e, { level: props.lavel!, parent: props.parentData, data: e });
}

实现方法千千万,首先需要滤清思路~~

欢迎前端交流

QQ群:362909884(禁止广告、内置GPT机器人)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值