当前位置: 首页 > 工具软件 > Tree > 使用案例 >

react实现tree组件

穆宾白
2023-12-01

一: 数据量小的结构

index.less---------------------
li > ul{
    max-height: 0;
    transition: all .2s;
    overflow: hidden;
}
li.cur > ul{
    max-height: 500px;
    transition: max-height .4s;
}

creatNode.js:-----------------
import React, { useState, useEffect } from 'react';
import './index.less';

function CreatNode(props){
    return (
        <ul className="ul">
            {
                props.tree&&props.tree.map(item => {
                    return(
                        <Node {...item} handle={props.handle} key={item.id}>
                            <CreatNode handle={props.handle} tree={item.children} key={item.id}/>
                        </Node>
                    )
                })
            }
        </ul>
    );
}

function Node(props){
    const [cur, setCur] = useState(false);
    let listclick = (e) => {
        props.handle(e, {
            name: props.name,
            id: props.id,
            parentId: props.parentId
        })
        setCur(!cur)
    }
    return(
        <li className={cur ? "cur" : ""}>
            <p onClick={listclick}>{props.name}</p>
            {props.children}
        </li>
    );
}

export default CreatNode

tree.js-------------------------------------------
import React, {Component} from 'react';
import CreatNode from './creatNode';

class Tree extends Component {
 
  render() {
    return (<CreatNode handle={this.props.handle} tree={this.totree(this.props.data,0)}/>);
  }

  totree(list,parId) {
    let obj = {};
    let result = [];
    //将数组中数据转为键值对结构 (这里的数组和obj会相互引用)
    list.map(el => {
        obj[el.id] = el;
    })
    for(let i=0, len = list.length; i < len; i++) {
        let id = list[i].parentId;
        if(id == parId) {
            result.push(list[i]);
            continue;
        }
        if(obj[id].children) {
            obj[id].children.push(list[i]);
        } else {
            obj[id].children = [list[i]];
        }
    }
    return result;
  }

}

export default Tree

使用:
import React, {Component} from 'react';
import Tree from '../components/mytree';

class TestTree extends Component {
  state = {
      treeData:[
        {id:2,name:'部门B',parentId:0},
        {id:3,name:'部门C',parentId:1},
        {id:10,name:'部门11111',parentId:7},
        {id:1,name:'部门A',parentId:2},
        {id:4,name:'部门D',parentId:1},
        {id:5,name:'部门E',parentId:2},
        {id:6,name:'部门F',parentId:3},
        {id:7,name:'部门G',parentId:2},
        {id:8,name:'部门H',parentId:4}
    ]
  }

  listClick = (e, data) => {
    console.log(data)
  }

  render() {
    return (
        <div>
            <Tree data={this.state.treeData} handle={this.listClick}/>
        </div>
    );
  }
}

export default TestTree

二:以上的方式适合少量的数据
creatNode.js:

export default (n) => {
    const nodes = [
      {
        id: 0,
        name: "节点0",
        pid: -1
      }
    ];
    let i = 1;
    while (i < n) {
        nodes.push({
            id: i,
            name: `节点${i}`,
            pid: 0,
        });
        i++;
    }
    return nodes;
  };
  

utils.js

export const isEmpty = data => {
  if (
    data === null ||
    data === undefined ||
    `${data}`.trim() === "" ||
    (data !== 0 && !data)
  ) {
    return true;
  }
  return false;
};

export const treeNode = ({ id, pid, name, ...extra }) => {
  const empty = isEmpty(id) || isEmpty(pid);
  if (empty) {
    console.error(
      { id, pid },
      "id和pid不能为null, undefined,NaN,以及空字符串."
    );
    return;
  }

  return {
    id,
    name,
    pid,
    ...extra
  };
};

/**
 * @param {object[]} arr 原始节点数组
 * @returns {Object} 改造后的节点数组增加chilren、parentIndex
 * 和index属性数组的第一个元素为根节点
 */
export const arrayToTreeNodesArray = arr => {
  if (!Array.isArray(arr)) {
    console.error("参数必须是数组");
    return;
  }

  const nodes = {};

  // 转换成普通Object形式, 记录index
  arr.forEach((item, index) => {
    const nodeItem = treeNode(item);
    if (!nodeItem) return;
    nodes[nodeItem.id] = index;
  });
  
  const treeNodeArr = [...arr];
  let rootNodeIndex;
  Object.keys(nodes).find(key => {
    const index = nodes[key];
    const currentNode = arr[index];
    const { pid } = currentNode;
    if (`${pid}` === "-1" && (rootNodeIndex || rootNodeIndex === 0)) {
      console.error("不止一个根节点", {
        rootNodeIndexes: [rootNodeIndex, index]
      });
      return true;
    } else if (`${pid}` === "-1") {
      rootNodeIndex = index;
    }
    let parentIndex = nodes[pid];
    treeNodeArr[index] = { ...treeNodeArr[index], parentIndex, index };
    if ((!parentIndex && parentIndex !== 0) || `${parentIndex}` === "-1")
      return false;

    if (!treeNodeArr[parentIndex].children) {
      treeNodeArr[parentIndex] = { ...treeNodeArr[parentIndex], children: [] };
    }
    treeNodeArr[parentIndex].children.push(index);
    return false;
  });

  if (rootNodeIndex || rootNodeIndex === 0) {
    treeNodeArr[rootNodeIndex].hidden = false;
    return { treeNodeArr, rootNodeIndex };
  }
  console.error("没有根节点");
  return;
};

/**
 * @param {object} treeObject 将形如{id, name, pid, children}
 * 的object数据转换为树形结构数组
 */
export const objectToTreeNodesArray = treeObject => {
  const treeNodeArr = [];
  function objectToTreeArr(sourceObject, parentIndex) {
    if (!sourceObject.children || sourceObject.children.length === 0) {
      treeNodeArr.push({
        ...sourceObject,
        parentIndex,
        index: treeNodeArr.length
      });
      return;
    }

    const { children, ...rest } = sourceObject;
    const currentNode = {
      ...rest,
      children: [],
      parentIndex,
      index: treeNodeArr.length
    };
    treeNodeArr.push(currentNode);
    for (let i = 0, len = children.length; i < len; i++) {
      currentNode.children.push(treeNodeArr.length);
      objectToTreeArr(children[i], currentNode.index);
    }
  }
  objectToTreeArr(treeObject);
  return { treeNodeArr, rootNodeIndex: 0 };
};

export const createTreeNodesArray = treeNodes => {
  if (Array.isArray(treeNodes)) {
    return arrayToTreeNodesArray(treeNodes);
  }
  if (typeof treeNodes === "object") {
    return objectToTreeNodesArray(treeNodes);
  }
};

export const changeExpandStatus = (node, treeNodes) => {
  const { children, expanded = false } = node || {};
  if (!children || children.length === 0) return;
  const nodes = [...treeNodes];
  nodes[node.index] = { ...node, expanded: !expanded };
  children.forEach(item => {
    nodes[item] = { ...nodes[item], rendered: !expanded };
  });
  return nodes;
};

tree.js:

import React, { useState, useEffect } from "react";
import { createTreeNodesArray, changeExpandStatus } from "../Tree/tree.api";
import "./tree.less";

const TreeNodes = props => {
  const { treeNodes, rootNodeIndex, onNodeClick, render } = props;
  
  const rootNode = treeNodes[rootNodeIndex];
  if (!treeNodes || treeNodes.length === 0) return "";
  return (
    <BranchNode
      onNodeClick={onNodeClick}
      render={render}
      level={0}
      treeNodes={treeNodes}
      branch={rootNode}
    />
  );
};

const BranchNode = ({ branch, onNodeClick, treeNodes, level, render }) => {
  const { children, hidden = false, expanded = false } = branch || {};
  if (hidden) return "";
  console.log(render)
  const isLeafNode = !children || children.length === 0;
  const iconClass = expanded ? "icon-jiantou_xia" : "icon-jiantou_you";
  return (
    <li className={`${isLeafNode ? "tree-leaf-node" : ""} ${iconClass} tree-li`}>
      <span
        onClick={e => onNodeClick(e, branch)}
        className={` ${
          isLeafNode ? "" : "branch-icon-text-container"
        } level${level}`}
      >
        {!isLeafNode && (
          <span className={`iconfont ${iconClass} branch-icon`} />
        )}
        <span>
          {render && typeof render === "function"
            ? render({ node: branch, level, treeNodes })
            : branch.name}
        </span>
      </span>
      {expanded && !isLeafNode && (
        <ul className="tree-branch-node">
          {children.map(item => {
            const newBranch = treeNodes[item];
            if (!newBranch) return "";
            return (
              <BranchNode
                key={`branch-${newBranch.id}`}
                branch={newBranch}
                onNodeClick={onNodeClick}
                treeNodes={treeNodes}
                level={level + 1}
                render={render}
              />
            );
          })}
        </ul>
      )}
    </li>
  );
};

const Tree = props => {
  const { treeNodes, onNodeClick, render } = props;
  
  const [nodesArrObj, setNodesArrObj] = useState(null);
  
  useEffect(() => {
    const nodesObject = createTreeNodesArray(treeNodes);
    setNodesArrObj(nodesObject);
  }, [treeNodes]);

  const { rootNodeIndex, treeNodeArr } = nodesArrObj || {};

  const nodeClickHandler = (e, node) => {
    e.stopPropagation();
    onNodeClick && onNodeClick(e, node);
    const newNodesArr = changeExpandStatus(node, treeNodeArr);
    if (newNodesArr && newNodesArr.length > 0) {
      setNodesArrObj({ rootNodeIndex, treeNodeArr: newNodesArr });
    }
  };

  if (
    (!rootNodeIndex && rootNodeIndex !== 0) ||
    !treeNodeArr ||
    !treeNodeArr.length
  ){
    return "";
  }

  return (
    <ul className="tree-container">
      <TreeNodes
        rootNodeIndex={rootNodeIndex}
        treeNodes={treeNodeArr}
        onNodeClick={nodeClickHandler}
        render={render}
      />
    </ul>
  );
};

export default Tree;


使用:

import React from "react";
// import Tree from "../components/Tree/Tree";
import Tree from "../components/MtTree/tree";
import createNodes from "../components/Tree/creatNode";

export default class TreeTest extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      objectData: {
        id: 0,
        name: "根节点",
        pid: -1,
        children: [
          {
            id: 1,
            name: "节点0-1",
            pid: 0,
            children: [
              {
                id: 6,
                name: "节点0-1-6",
                pid: 1
              }
            ]
          },
          {
            id: 2,
            name: "节点0-2",
            pid: 0
          },
          {
            id: 3,
            name: "节点0-3",
            pid: 0,
            children: [
              {
                id: 4,
                name: "节点0-3-4",
                pid: 3
              },
              {
                id: 5,
                name: "节点0-3-5",
                pid: 3
              }
            ]
          }
        ]
      },
      arrayData: [
        {
          id: 5,
          name: "节点0-3-5",
          pid: 3
        },
        {
          id: 4,
          name: "节点0-3-4",
          pid: 3
        },
        {
          id: 0,
          name: "根节点",
          pid: -1
        },
        {
          id: 6,
          name: "节点0-1-6",
          pid: 1
        },
        {
          id: 2,
          name: "节点0-2",
          pid: 0
        },
        {
          id: 3,
          name: "节点0-3",
          pid: 0
        },
        {
          id: 1,
          name: "节点0-1",
          pid: 0
        }
      ]
    };

    //创建数据
    const arrayData = createNodes(100);
    this.state.arrayData = arrayData;
    
  }

  onNodeClick = (e, node) => {
    // this.state.arrayData = arrayData;
    // console.info("优化Tree组件TreeTest---onNodeClick---", node);
  };

  render() {
    return (
      <div id="tree">
        <h4>数组格式的节点数据:</h4>
        <Tree onNodeClick={this.onNodeClick} 
              treeNodes={this.state.arrayData}
              render={
                data => {
                  return <p>{data.node.name}自定义一些东西</p>
                }
              }>
        </Tree>
        <h4>Object格式的节点数据:</h4>
        <Tree
          onNodeClick={this.onNodeClick}
          treeNodes={this.state.objectData}
        />
      </div>
    );
  }
}


第二种方式
优点:加载大量数据,点击时再加载,关闭标签节点移除
还有点击动态加载的方式,实现中…

 类似资料: