最近在react项目中需要一个树状组件,但是又不想因为这个去引入一套UI组件,故自己封装了一个基于react的树状组件,

个人认为比较难得部分在于数据的处理,话不多说直接上代码:

下面是tree.js

import React, {Component} from 'react';
import './tree.css';
import Stack from '../utils/util'; class Tree extends Component {
constructor(props) {
super(props)
this.state = {
treeData: {},
treeArray: [],
treeObj: {},
type: 'tree',
parentId: 'pid',
id: 'id',
value: 'value',
label: 'label',
children: 'children',
checkBox: false
}
this.checkMap = {
2: 'checked',
1: 'partChecked',
0: ''
}
} componentWillMount() {
if (this.props.config.type.toLowerCase() === 'tree') {
this.setState({
treeData: this.props.treeData,
...this.props.config
})
} else {
this.setState({
treeArray: this.props.treeData,
...this.props.config
})
}
} componentDidMount() {
if (this.state.type.toLowerCase() !== 'tree') {
this.factoryArrayData()
} else {
this.factoryTreeData()
}
} componentDidUpdate() { } componentWillUnmount() { } factoryArrayData() {
let data = this.state.treeArray, obj = {}, rootId = null;
data.map((v, i) => {
if (v[this.state.parentId] || v[this.state.parentId] === 0) {
if (obj[v[this.state.parentId]]) {
if (obj[v[this.state.parentId]].children) {
obj[v[this.state.parentId]].children.push(v)
} else {
obj[v[this.state.parentId]].children = [v]
}
} else {
obj[v[this.state.parentId]] = {
children: [v]
}
}
} else {
rootId = v[this.state.id]
}
if (obj[v[this.state.id]]) {
v.children = obj[v[this.state.id]].children
}
obj[v[this.state.id]] = v
})
this.setState({
treeData: obj[rootId],
treeObj: obj
})
} factoryTreeData() {
let data = this.state.treeData
let stack = new Stack();
let obj = {};
stack.push(data);
while (stack.top) {
let node = stack.pop();
for (let i in node.children) {
stack.push(node.children[i])
}
obj[node[this.state.id]] = node
}
this.setState({
treeObj: obj
})
} openNode (e, data) {
if (e.stopPropagation) {
e.stopPropagation();
} else {
window.event.cancelBubble = true;
}
data.open = !data.open
this.forceUpdate()
} selectNode (e, data) {
if (e.stopPropagation) {
e.stopPropagation();
} else {
window.event.cancelBubble = true;
}
this.setState({
selectVal: data[this.state.value]
}, () => {
if (this.props.nodeClick) {
this.props.nodeClick(data[this.state.value])
}
})
} selectCheckBox (e, data) {
if (e.stopPropagation) {
e.stopPropagation();
} else {
window.event.cancelBubble = true;
}
let check = data.checked
if (data.children && data.children.length) {
let stack = new Stack();
stack.push(data);
while(stack.top) {
let node = stack.pop()
for (let i in node.children) {
stack.push(node.children[i])
}
if (check === 2) {
node.checked = 0;
} else {
node.checked = 2
}
}
} else {
if (check === 2) {
data.checked = 0;
} else {
data.checked = 2
}
}
if (data[this.state.parentId] || data[this.state.parentId] === 0) {
this.updateParentNode(data)
} else {
this.forceUpdate()
if (this.props.selectChange) {
this.getCheckedItems()
}
}
} updateParentNode (data) {
let par = this.state.treeObj[data[this.state.parentId]], checkLen = 0, partChecked = false;
for (let i in par.children) {
if (par.children[i].checked === 2) {
checkLen++;
} else if (par.children[i].checked === 1) {
partChecked = true;
break;
}
}
if (checkLen === par.children.length) {
par.checked = 2
} else if (partChecked || (checkLen < par.children.length && checkLen > 0)) {
par.checked = 1;
} else {
par.checked = 0;
}
if (this.state.treeObj[par[this.state.parentId]] || this.state.treeObj[par[this.state.parentId]] == 0) {
this.updateParentNode(par)
} else {
this.forceUpdate()
if (this.props.selectChange) {
this.getCheckedItems()
}
}
} getCheckedItems() {
let stack = new Stack ();
let checkedArr = [];
stack.push(this.state.treeData);
while (stack.top) {
let node = stack.pop();
for (let i in node.children) {
stack.push(node.children[i])
}
if (node.checked === 2) {
checkedArr.push(node[this.state.value])
}
}
this.props.selectChange(checkedArr)
} renderTreeParent() {
let data = this.state.treeData
return (
<div className={`parentNode childNode ${data.open?'open':'close'} ${data.children && data.children.length?'':'noChildren'}`}>
<span onClick={(e) => this.openNode(e, data)} className="openNode"></span>
{
this.state.checkBox?
<div className={`checkBox ${this.checkMap[data.checked]}`} onClick={(e) => this.selectCheckBox(e, data)}></div>:
<div className="fileBox">
<img src="./images/file-icon.png" alt=""/>
</div>
}
<div className={`nodeName ${this.state.selectVal === data[this.state.value]?'active':''}`} onClick={(e) => this.selectNode(e, data)}>
{data[this.state.label]}
</div>
{
this.state.treeData.children ?
<div className="childList">
{this.renderTreeNode(data)}
</div> : null
}
</div>
)
} renderTreeNode(data) {
return data.children.map((val, ind) => {
return (
<div key={ind} className={`childNode ${val.open?'open':'close'} ${val.children && val.children.length?'':'noChildren'}`}>
<span onClick={(e) => this.openNode(e, val)} className="openNode"></span>
{
this.state.checkBox?
<div className={`checkBox ${this.checkMap[val.checked]}`} onClick={(e) => this.selectCheckBox(e, val)}></div>:
<div className="fileBox">
<img src="./images/file-icon.png" alt=""/>
</div>
}
{ind === data.children.length - 1?
<span className="lastNode"></span>:null
}
<div className={`nodeName ${this.state.selectVal === val[this.state.value]?'active':''}`} onClick={(e) => this.selectNode(e, val)}>
{val[this.state.label]}
</div>
{
val.children ?
<div className="childList">
{this.renderTreeNode(val)}
</div> : null
}
</div>
)
})
} render() {
return (
<div className="tree">
{this.renderTreeParent()}
</div>
)
}
} export default Tree

下面是tree.css

.tree {
text-align: left;
}
.tree .childNode {
padding-left: 20px;
position: relative;
background-color: #ffffff;
z-index:;
}
.tree .childNode .checkBox {
position: absolute;
width: 16px;
left: 20px;
top:;
z-index:;
margin: 7px 10px 0;
height: 16px;
box-sizing: border-box;
border: 1px solid #d2d2d2;
vertical-align: text-bottom;
font-size:;
border-radius: 2px;
cursor: pointer;
}
.tree .childNode .checkBox:hover {
cursor: pointer;
border-color: #5bb976;
}
.tree .childNode .checkBox.checked {
border:;
background: url(../images/icon-check-green.png) no-repeat center center;
background-size: 100% 100%;
background: none\9;
filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='./images/icon-check-green.png', sizingMethod='scale') \9;
}
.tree .childNode .checkBox.partChecked {
border:;
background: url(../images/part-checked.png) no-repeat center center;
background-size: 100% 100%;
background: none\9;
filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='./images/part-checked.png', sizingMethod='scale') \9;
}
.tree .childNode .nodeName {
padding-left: 36px;
font-size: 14px;
color: #333333;
white-space: nowrap;
overflow: hidden;
line-height: 30px;
height: 30px;
text-overflow: ellipsis;
position: relative;
z-index:;
display: inline-block;
padding-right: 10px;
}
.tree .childNode .nodeName.active {
background-color: #DEF1FF;
}
.tree .childNode .nodeName:hover {
text-decoration: underline;
cursor: pointer;
}
.tree .childNode.open .openNode {
background: url(../images/department-close.png) no-repeat center center;
background-size: 100% 100%;
background: none\9;
filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='./images/department-close.png', sizingMethod='scale') \9;
}
.tree .childNode.open .childList {
display: block;
}
.tree .childNode.close .openNode {
background: url(../images/depart-open.png) no-repeat center center;
background-size: 100% 100%;
background: none\9;
filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='./images/depart-open.png', sizingMethod='scale') \9;
}
.tree .childNode.close .childList {
display: none;
}
.tree .childNode .fileBox {
position: absolute;
width: 16px;
left: 20px;
top:;
margin: 5px 10px 0;
z-index:;
}
.tree .childNode .fileBox:hover {
cursor: pointer;
}
.tree .childNode .fileBox img {
width: 16px;
}
.tree .childNode:before {
position: absolute;
left: -13px;
top: 15px;
width: 20px;
height: 100%;
border-top: 1px solid #CFCFCF;
border-right: 1px solid #CFCFCF;
content: '';
z-index:;
}
.tree .childNode:after {
position: absolute;
bottom: -12px;
left: 7px;
width: 1px;
height: 30px;
z-index:;
background-color: #ffffff;
content: '';
}
.tree .childNode.parentNode:before {
border-top: none;
}
.tree .childNode .openNode {
position: absolute;
z-index:;
left:;
top: 8px;
width: 14px;
height: 14px;
}
.tree .childNode .openNode:hover {
cursor: pointer;
}
.tree .childNode.noChildren .openNode {
width: 10px;
height: 10px;
top: 10px;
left: 7px;
background: url(../images/no-child.png) no-repeat center center;
background-size: 100% 100%;
background: none\9;
filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='./images/no-child.png', sizingMethod='scale') \9;
}
.tree .childNode.noChildren .openNode:hover {
cursor: default;
}
.tree .childNode .lastNode {
position: absolute;
bottom: -15px;
left: -13px;
width: 1px;
height: 100%;
z-index:;
background-color: #ffffff;
}

utils里面是封装了一个stack栈,关于js栈的使用请移步js遍历树状数据文章。

github项目地址

react树状组件的更多相关文章

  1. Java Swing 树状组件JTree的使用方法(转)

    树中特定的节点可以由 TreePath(封装节点及其所有祖先的对象)标识,或由其显示行(其中显示区域中的每一行都显示一个节点)标识.展开 节点是一个非叶节点(由返回 false 的 TreeModel ...

  2. react 使用antd的TreeSelect树选择组件实现多个树选择循环

    需求说明,一个帐号角色可以设置管理多个项目的菜单权限 且菜单接口每次只能查询特定项目的菜单数据[无法查全部] 开发思路: 1,获取项目接口数组,得到项目数据 2,循环项目数据,以此为参数递归查询菜单数 ...

  3. 聊聊React高阶组件(Higher-Order Components)

    使用 react已经有不短的时间了,最近看到关于 react高阶组件的一篇文章,看了之后顿时眼前一亮,对于我这种还在新手村晃荡.一切朝着打怪升级看齐的小喽啰来说,像这种难度不是太高同时门槛也不是那么低 ...

  4. EasyUI + ajax + treegrid/datagrid 接收 json 数据,显示树状/网状表结构

    最后一更了,时间间隔有点久了~~ EasyUI作为一个成熟的前端框架,封装了ajax,对于数据的处理配合datagrid组件的使用,使其非常适合后台管理界面的开发(目前来说界面有点过时了). 通过aj ...

  5. vue 树状图数据的循环 递归循环

    在main.js中注册一个子组件 在父组件中引用 树状图的数据格式 绑定一个数据传入子组件,子组件props接收数据 子组件中循环调用组件,就实现了递归循环

  6. Android中的树状(tree)列表

    树状列表前端挺常用的,还有人专门写过Ztree,Android中有的时候也需要使用到树状列表,上篇文章写了一下ExpandableListView,ExpandableListView最多支持两级结构 ...

  7. Tkinter 之TreeView表格与树状标签

    一.TreeView介绍 TreeView组件是一个树状结构和表格的结合体.第一列是树状结构,后几列是列表.每一行表示一个item,树的item可以分级,每个item有子item,名称对应text标签 ...

  8. 手把手教学~基于element封装tree树状下拉框

    在日常项目开发中,树状下拉框的需求还是比较常见的,但是element并没有这种组件以供使用.在这里,小编就基于element如何封装一个树状下拉框做个详细的介绍. 通过这篇文章,你可以了解学习到一个树 ...

  9. jquery-treegrid树状表格的使用(.Net平台)

    上一篇介绍了DataTable,这一篇在DT的基础之上再使用jquery的一款插件:treegrid,官网地址:http://maxazan.github.io/jquery-treegrid/ 一. ...

随机推荐

  1. Gson本地和服务器环境不同遇到的Date转换问题 Failed to parse date []: Invalid time zone indicator

    GoogleGson在处理Date格式时有个小陷阱,在不同环境中部署时可能会遇到问题. Gson默认处理Date对象的序列化/反序列化是通过一个SimpleDateFormat对象来实现的,通过下面的 ...

  2. Git Reference

    Installing and upgrading Git https://confluence.atlassian.com/bitbucketserver056/installing-and-upgr ...

  3. JS模块

    本文主要内容翻译自<Exploring ES6-upgrade to the next version of javascript> 模块系统 定义模块接口,通过接口 隐藏模块的内部实现, ...

  4. JMeter - 如何在多个测试环境中运行多个线程组

    概述: 作为性能测试的一部分,我不得不为我们的应用程序提供各种用例/业务工作流程的性能测试脚本.当我设计我的性能测试脚本时,我将确保我有本文中提到的可重用测试脚本. JMeter - 如何创建可重用和 ...

  5. array类型的方法

    var arr1 = [12,454,'dafda','feagfag',23]; var arr2 = [46468,'ffwfe','dafs','dfsfs']; arr1.indexOf('d ...

  6. maven jetty 插件 允许修改 js

    <!--允许修改js,css--> <servlet> <servlet-name>default</servlet-name> <init-pa ...

  7. BZOJ 3673 可持久化并查集 by zky && BZOJ 3674 可持久化并查集加强版 可持久化线段树

    既然有了可持久化数组,就有可持久化并查集.. 由于上课讲过说是只能按秩合并(但是我也不确定...),所以就先写了按秩合并,相当于是维护fa[]和rk[] getf就是在这棵树中找,直到找到一个点的fa ...

  8. jquery $.fn $.fx 的意思以及使用

    $.fn是指jquery的命名空间,加上fn上的方法及属性,会对jquery实例每一个有效,下面简单为大家介绍下jquery $.fn $.fx到底是怎么一回事 $.fn是指jquery的命名空间,加 ...

  9. JS——操作元素属性

    属性的操作包括:读和写 方法: 1)”.“操作 2)”[ ]“操作 eg: var oDiv = document.getElementById('div1'); var attr = 'color' ...

  10. 030 Substring with Concatenation of All Words 与所有单词相关联的字串

    给定一个字符串 s 和一些长度相同的单词 words,找出 s 与 words 中所有单词(words 每个单词只出现一次)串联一起(words中组成串联串的单词的顺序随意)的字符串匹配的所有起始索引 ...