知识点记录:树的扁平化以及扁平化数据转为树

文章讲述了如何使用Vue2和ElementUI开发一个支持树形数据穿梭的组件,关键步骤包括数据扁平化、处理v-model选中ID集合以及重构树结构。作者详细描述了如何通过递归操作将多维树数据转换为一维并保持选中数据的独立性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近做业务的时候,有个树穿梭器的需求,这个业务所用的技术栈是vue2+element Ui,由于element UI的穿梭器仅支持checkbox形式,所以自己去开发这个组件了,在这个组件中用到的一些逻辑还是涉及到了很多知识点的,所以在此记录一下。

在这里插入图片描述

首先,这个组件的UI大概是这样的:

在这里插入图片描述

这里仅记录一下其中的核心逻辑。
从UI上看很显而易见,大致就是操作一个接口返回来的树数据,通过v-model传入的选中id集合 selectIds,将原始树数据拆分为左边的 leftTreeData 和右边的 rightTreeData 并渲染到对应区域中即可,两个拆分出来的树数据,相互不能包含对方的末级子数据,穿梭器嘛,实际上就是把左边的数据剪切到右边来,单纯的数据还好说,放在树结构当中,就比较复杂了。
这里我的做法是先把整个树数据扁平化,多维数据转化为一维数据,其中包含所有的父子级数据,具体方法是:

const treeData = [
  {
    title: "001",
    id: 1000,
    children: [
      {
        title: "001-01",
        id: 1100,
        children: [
          { title: "001-01-01", id: 1101 },
          { title: "001-01-01", id: 1102 },
          { title: "001-01-03", id: 1103 },
          { title: "001-01-04", id: 1104 },
        ],
      },
      {
        title: "001-02",
        id: 1200,
        children: [
          { title: "001-02-01", id: 1201 },
          { title: "001-02-02", id: 1202 },
        ],
      },
      {
        title: "001-03",
        id: 1300,
        children: [{ title: "001-03-01", id: 1301 }],
      },
      {
        title: "001-04",
        id: 1400,
        children: [{ title: "001-04-01", id: 1401 }],
      },
    ],
  },
  {
    title: "002",
    id: 2000,
    children: [
      {
        title: "002-01",
        id: 2100,
        children: [
          { title: "002-01-01", id: 2101 },
          { title: "002-01-01", id: 2102 },
          { title: "002-01-03", id: 2103 },
          { title: "002-01-04", id: 2104 },
        ],
      },
      {
        title: "002-02",
        id: 2200,
        children: [
          { title: "002-02-01", id: 2201 },
          { title: "002-02-02", id: 2202 },
          { title: "002-02-03", id: 2203 },
        ],
      },
      {
        title: "002-03",
        id: 2300,
        children: [
          { title: "002-03-01", id: 2301 },
          { title: "002-03-02", id: 2302 },
        ],
      },
      {
        title: "002-04",
        id: 2400,
        children: [{ title: "002-04-01", id: 2401 }],
      },
    ],
  },
  {
    title: "003",
    id: 3000,
    children: [
      {
        title: "003-01",
        id: 3100,
        children: [
          { title: "003-01-01", id: 3101 },
          { title: "003-01-01", id: 3102 },
          { title: "003-01-03", id: 3103 },
          { title: "003-01-04", id: 3104 },
          { title: "003-01-05", id: 3105 },
        ],
      },
    ],
  },
];
// 将树数据扁平化
// 这里利用reduce的特性对原始的树数据进行递归遍历,最终返回一个map结构数据,并主动为数据添加一个父级标识pid
// 这里也可以修改成返回一个array类型数据,之所以用map是为了方便后续的数据获取和去重
const childrenKey = 'children'
const _nodeKey = 'id'
const rootPid ='0'
const treeToFlat = (arr, pid, map) =>
  arr.reduce((prev, cur) => {
    const cur_children = cur[childrenKey];
    const cur_id = cur[_nodeKey];
    const newCur = JSON.parse(
      JSON.stringify({ pid: pid || rootPid, ...cur })
    );
    Reflect.deleteProperty(newCur, childrenKey);
    prev.set(cur_id, newCur);
    if (Array.isArray(cur_children) && cur_children.length)
      treeToFlat(cur_children, cur_id, prev);
    return prev;
  }, map || new Map());

const flatArrayMap = treeToFlat(treeData)

在这里插入图片描述

获取到扁平化后的一维数据 flatArrayMap 之后,再遍历选中的 id 集合,根据每一个 id 从 flatArrayMap 中获取这个 id 的对象数据,并获取这个 id 的父级数据 item1,父级的父级 item2···直至最顶级 item[n],再将这些数据组装成数组 [item1,item2…item[n]],最后将这些数据添加到一个新的map集合中,至于为什么是map,其实还是为了减少递归和去重,具体实现方法是:

// 通过keys集合,获取扁平化后的选中数组集合
const selectIdsToFlat = (keys, flatMap) => {
  const KeyFlatArrMap = new Map();
  if (Array.isArray(keys) && keys.length)
    keys.forEach((key) => {
      const keyToFlat = (key, map) => {
        return [key].reduce((prev, cur) => {
          const cur_obj = map.get(cur);
          const { pid } = cur_obj || {};
          const cur_arr =
            pid && pid !== rootPid ? keyToFlat(pid, map) : [];
          return prev.concat([cur_obj], cur_arr);
        }, []);
      };
      const keyFlatArr = keyToFlat(key, flatMap)
        .reverse()
        .filter((k) => !!k);
      keyFlatArr.forEach((item) => {
        KeyFlatArrMap.set(item[_nodeKey], item);
      });
    });
  return KeyFlatArrMap;
};

const selectIdsToArrayMap = selectIdsToFlat([1101,1201,1301,2101,3101],flatArrayMap);
const resultItems = [...selectIdsToArrayMap.values()]

![在这里插入图片描述](https://img-blog.csdnimg.cn/92bac21492b34112b3ac1575750777fa.png

最后将获取到的resultItems再组装成新的树,就可以得到选中的id集合汇成的新树数据了:

const _nodeKey = 'id'
const childrenKey = 'children'
 //格式化扁平化数据,将扁平化数据转化为树数据
const formatFlagToTree = (baseArr, parentId) => {
  const map = baseArr.reduce((prev, cur) => {
    prev[cur[_nodeKey]] = cur;
    return prev;
  }, {});
  let result = [];
  for (let i = 0; i < baseArr.length; i++) {
    const item = baseArr[i];
    if (item.pid === parentId) {
      result.push(item);
      continue;
    }
    const parent = map[item.pid];
    if (parent) {
      parent[childrenKey] = parent[childrenKey] || [];
      parent[childrenKey].push(item);
    }
  }
  return result;
}
const newTree = formatFlagToTree(resultItems , "0");

在这里插入图片描述

以上便是树穿梭器的核心逻辑啦,既然能通过选中的id集合获取到对应的选中树rightTreeData渲染右边区域;
那也能通过选中的id,在所有id集合中过滤一下,就能得到所有未选中的id集合,再生成未选中的树leftTreeData渲染到左边区域;
至此穿梭器的逻辑起差不多完成了。
在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端小乖巧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值