antd+ReactJs玩转tree结构数据

3,936 阅读6分钟

前言:天下寥寥,苍生涂涂,各路编程,唯我前端。一直热衷前端,却只是初窥门径,寻提升之路未果,故只能通过叙述的方式记录工作中遇到的问题,但求表达明了,增进自己对前端知识的理解。tree结构数据在工作经常碰到,今天写篇文章记录下操作tree形状数据的心得。

文章核心内容以三分

  • 一者,扁平数据转tree形数据
  • 二者,tree形数据转扁平数据
  • 后者(目标):结合antd,reactJs实现丝滑的search Tree(搜索树)

扁平数据转tree数据

  • 首先,随意书写一段有关系的数据:

const data = [
  { name: "马云", key: 1 },
  { name: "前端技术专家", key: 3, parent: 2 },
  { name: "首席科学家", key: 2, parent: 1 },
  { name: "前端架构师", key: 4, parent: 3 },
  { name: "前端工程师", key: 5, parent: 4 },
  { name: "前端菜鸟", key: 6, parent: 5 },
  { name: "前端小白", key: 7, parent: 6 },
  { name: "马岳父", key: 8 },
  { name: "QQ", key: 9, parent: 8 },
  { name: "微信", key: 10, parent: 8 },
  { name: "王者荣耀", key: 11, parent: 8 },
  { name: "绝地求生", key: 12, parent: 8 },
  { name: "QQ会员", key: 13, parent: 9 },
  { name: "QQ空间", key: 14, parent: 9 },
  { name: "QQ钱包", key: 15, parent: 9 },
  { name: "沙漠地图", key: 16, parent: 12 },
  { name: "公众号", key: 17, parent: 10 },
  { name: "群聊", key: 18, parent: 10 },
  { name: "小程序", key: 19, parent: 10 },
  { name: "花木兰", key: 20, parent: 11 },
  { name: "芈月", key: 21, parent: 11 },
  { name: "马可波罗", key: 22, parent: 11 },
];
  • 我写的数据最顶层父级是马云马化腾,根据阿里职级标准,我们希望得到的结果是这样的:

image.png

  • 根据化腾岳父和他的吸金帝国关系,我们希望得到的结果是这样的:

af6a924be5a7f4059a63c01150a0559.png

  • 那么问题来了,如何得到这样的层次结构数据?我们一个一个来看;数据中的key代表当前数据的唯一标识,而parent则代表其父级。那么很明显,我们要做的就是:遍历data数组,根据parent和key的对应关系,生成tree数据,这里我们用children来装子数据。 话不多说,直接上才艺。
//封装getTree方法,将数组转化为tree.

import { cloneDeep } from "lodash";//lodash库里引入cloneDeep深克隆方法

const getTree = (data) => {

  const temp = cloneDeep(data);//深克隆一份外来数据data,以防下面的处理修改data本身
  const parents = temp.filter((item) => !item.parent); //过滤出最高父集
  const children = temp.filter((item) => item.parent);//过滤出孩子节点
  
  //遍历孩子节点,根据孩子的parent从temp里面寻找对应的node节点,将孩子添加在node的children属性之中。
  children.map((item) => {
    const node = temp.find((el) => el.key === item.parent);
    node && (node.children ? node.children.push(item) : node.children = [item]);
  });
  return parents;//返回拼装好的数据。
};
  • 封装好geTree方法,调用:
const tree=getTree(data);
console.log(tree);
  • 打印结果马云如下:

image.png

  • 化腾岳父如下:

image.png

  • 数组转tree的内容到这里就结束了,有疑惑的朋友可以评论区留言讨论。

tree数据转扁平数据

  • 数组转tree会了,那接下来就是tree转数组了。
  • 先随意定义一段数据:
const treeData = [
  {
    title: "太极",
    key: 1,
    children: [
      {
        title: "两仪",
        key: 2,
        parent: 1,
        children: [
          {
            title: "老阳",
            key: 3,
            parent: 2,
            children: [{ title: "乾卦", key: 7, parent: 3 }],
          },
          {
            title: "少阳",
            key: 4,
            parent: 2,
            children: [
              { title: "巽卦", key: 9, parent: 4 },
              { title: "兑卦", key: 10, parent: 4 },
              { title: "离卦", key: 11, parent: 4 },
            ],
          },
          {
            title: "老阴",
            key: 5,
            parent: 2,
            children: [{ title: "坤卦", key: 8, parent: 5 }],
          },
          {
            title: "少阴",
            key: 6,
            parent: 2,
            children: [
              { title: "艮卦", key: 12, parent: 6 },
              { title: "坎卦", key: 13, parent: 6 },
              { title: "震卦", key: 14, parent: 6 },
            ],
          },
        ],
      },
    ],
  },
];
  • 子曰:“加我数年,五十以学易,可以无大过矣。”可见孔子对易经的推崇。以上数据是易经中太极到八卦演化过程,甚为奇妙,可以说宇宙万象皆在这演化规律之中:太极-》两仪-》四象-》八卦。我们要做的就是将这一段tree形数据转化为扁平结构。话不多说,直接上才艺。
const getFlatData=(data)=>{
    const flatData=[];
    data.map(item=>{
      //解构当前item,去掉item中的children,将纯净的node(不含children的单条数据)添加到flatData中。如果children存在就递归执行getFlatData方法,直至添加了所有。
        const {children,...node}=item
        flatData.push(node);
        children&&flagData.push(...getFlatData(children));
    })
    return flatData;
};
  • 方法写好了,执行代码:
  console.log(getFlatData(treeData));
  • 效果如下:

image.png

  • tree数据转扁平结构到这就结束了,最后让我们来实现一个有趣的功能,搜索树。

目标:antd+ReactJs实现丝滑的搜索树

  • 首先,实现搜索框和树,我们默认展开所有的树节点,效果如下:

image.png

  • 当搜索时,我们希望只出现搜索到的包含关键字的项和他的父级,怎么理解?看图:
  • 当我们搜索“花木”时:

image.png

  • 当我们搜索“花”时:

image.png

  • 也就是说,当我们搜索时,我们要把原有的树结构多余部分拆解掉,只留下我们需要的部分。 怎么来实现呢?分为两步:第一步,找到包含关键字的项和他的父级。第二部:用我们之前封装好的getTree方法生成实时的tree结构。我们接下来就开始做。
  • 首先,改造之前封装的getFlatData方法,给tree的孩子增加一个parents属性,用来标识当前孩子所有的父级。如下:
const getFlatData = (data) => {
  const flagData = [];
  data.map((item) => {
    const { children, ...node } = item;
    flagData.push(node);
    
   +-----------
    children &&
      children.map((el) => {
        el.parents = (item.parents ? item.parents + "," : "") + item.key;
      }); //设置当前元素的所有父级。
   +-----------
   
    children && flagData.push(...getFlatData(children));
  });
  return flagData;
};
  • 其实就增加了虚线部分一句代码。效果如下:

image.png

  • 被处理过的数据会增加parents属性,标识当前元素的所有父级key值。

  • 接下来开始界面编程:

import React, { useRef, useState } from "react";
import { cloneDeep } from "lodash";
import { Tree, Input } from "antd";
import {
  DownOutlined,
} from "@ant-design/icons";

const SearchTree = () => {
  const [expands, setExpands] = useState(() => data.map((item) => item.key));//初始化tree展开的key值
  
  const flatData = useRef({});//ref变量,用来保存初始的tree数据和经过增强版getFlatData处理过的扁平结构数据。
  
  const [treeData, setTreeData] = useState(() => {//初始化tree数据。
    const tree = getTree(data);
    flatData.current.flat = getFlatData(tree);//保存增强版getFlatData处理过的扁平结构数据。
    flatData.current.tree = tree;//保存初始的tree数据
    return tree;
  });

  const handleSearch = (e) => {};

  return (
    <div
      style={{
        width: 399,
        height: 399,
        display: "flex",
        flexDirection: "column",
      }}
    >
      <Input onChange={handleSearch} />
      <Tree
        showIcon
        defaultExpandAll
        expandedKeys={expands}
        switcherIcon={<DownOutlined />}
        treeData={treeData}
      />
    </div>
  );
};

export default SearchTree;
  • 如图,界面很简易,只有一个输入框和一个Tree。初始化了三个state,三个state的作用看图上的解释。我们的重点在于实现handleSearch方法。

  • handleSearch方法实现如下:

const handleSearch = (e) => {
try {
  const v = e?.target?.value?.trim();
  if (!v?.length) {//搜索内容为空,初始化tree,默认全部展开

    setExpands(data.map((item) => item.key));
    setTreeData([...flatData.current.tree]);

  } else {//搜索内容不为空,获取搜索项和其父级,实时生成tree

    const keys = flatData.current.flat.filter((item) =>
      item?.title?.includes(v)
    );//获取搜索到的项

    if (!keys?.length) {//没有搜到,将tree置为空,比如搜索“木兰小宝贝”,就会什么都没有
      setExpands([]);
      setTreeData([]);
      return;
    }

    //以下逻辑处理keys不为空的情况

    const expandsTemp = [];//需要展开的节点的key
    const list = [];//存搜索项和其父级的数组
    list.push(...keys);//先将搜索到的项存入list;

    keys?.map((item) => {
      const data = item.parents?.split(",").filter(Boolean);//拆分parents属性,获取当前项所有父级
      data &&
        data.map((el) => {
          if (expandsTemp.every((_el) => _el !== el)) {
            expandsTemp.push(el);//此判断保证expandsTemp里key值不重复
          }
        });
    });

    expandsTemp.map((item) => {
      if (list.every((el) => el.key?.toString() !== item)) {//list里元素key值也要唯一。
        const node = flatData.current.flat.find(
          (el) => el.key?.toString() === item
        );
        node && list.push(node);
      }
    });//遍历expandsTemp,往list里添加搜索项的父级元素。

    const tempTree = getTree(list);//根据list组装tree
    setExpands([...expandsTemp.map((item) => Number(item))]);//map的作用是将expandsTemp里的key全部转为number;
    setTreeData([...tempTree]);//设置实时tree
  }
} catch (e) {}
};
  • search方法的说明如图,难点就在于通过tree孩子元素的parents属性找到所有父级,不重复的添加到一个数组中,最后组装成tree结构数据。

微信图片_20220101143232.jpg

完结语

  • 所有内容到这里就结束了,撒花!尼采曾说:每一个不曾起舞的日子,都是对生命的辜负!所以,让我们快乐起来,起舞吧!

QQ图片20220216164701.gif

QQ图片20220216164642.gif