一: 数据量小的结构
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>
);
}
}
第二种方式
优点:加载大量数据,点击时再加载,关闭标签节点移除
还有点击动态加载的方式,实现中…