一、前言
这个简单树组件也是自己为了熟悉react而做的,所以只是简单实现了基本功能,但是做完之后感觉其中也有很多需要自己总结的地方。其中还有几个未解决的问题自己在解决之后会修改这篇博文。话不多少,首先来看看我们到底要实现什么效果
我们的目标很简单,就是实现这样一个功能就行了,但是注意需要有一个半勾选的状态,就是父节点的某个子结点被选中了,那么这个结点就是半勾选状态。
二、思路分析
当我们有了目标之后,就可以先整理一下整体思路。树组件的根本思想就是维护一个数据结构,然后通过这个数据结构来管理各个子结点的渲染。其中最为重要的也是它的选中/半选/未选状态的改变。因为你改变一个结点的状态后会影响到其他结点。那么,我们可以有一个实现树组件非常直观的思路:
- 我给Tree组件传入一个完整的数据结构,类似下面这样:
[
{
name:"root",
children: [
{
name: "1",
children: [
{
name: "1-1",
children: []
}
]}
{
name: "2",
children: []
}
]
}
]
随后Tree组件利用这个数据结构自己将全部结点渲染出来。
- 在每个结点上保存子节已被全选的个数和半选的个数,通过和children的长度进行比较,确定当前结点的状态。
- 当一个结点被点击的时候,从头开始遍历整个数据结构,直到找到对应结点,同时,保存下找到结点经过的父节点。状态被影响了的结点就是这些父节点。
这样,我们就能实现一个树组件的基本功能。但是,这样有下面几个问题:
- 不易使用: 我们需要为tree组件传入一个完整的数据结构,当树很复杂时很难使用。
- 数据结构是一个树形结构,树很深时遍历会存在性能问题
- 每个结点的状态是由自己来判断的,我们应该有一个统一的机制,比如由父节点统一来判断各个子结点的状态,这样更加合理。
自己在最初实现的时候就是依照这个思路进行实现的,最后也实现了基本效果。随后自己在网上参考了一个开源项目进行实现,对思路进行了调整。项目地址为: https://github.com/react-component/tree。 该项目有500多个start,所以应该还是有一定参考意义的。别问我为什么没有看antd的源码,问就是看不懂。
所以,对思路进行调整之后,我们这样来实现一个树:
- 使用Tree + TreeNode的方式来创建一个树,具体可以参考antd树组件的使用方式,那么很显然,我们不能直接获得 一个完整的数据结构,所以需要自己在内部进行处理。同时,因为在Tree上有一些公有的属性,但是TreeNode没办法获得这些属性,在开源项目中,他是通过使用react的context来处理的,在我的实现中我采取了父组件一路通过props传递属性实现。
- 在判断子结点的状态时,我们可以直接为其传入一个bool值来决定他的状态。但是我们不可能在treeNode上手动计算这个bool值,所以我们需要做一层代理,根据某种逻辑计算出这个结点的状态,然后使用React.cloneElement在treeNode上加上他的状态。
- 对于子结点状态的判断,我们之前说过,应该由父节点统一决定。所以我们在父节点上维护几个数组,分别保存 全选/半选/展开 状态下各个结点的key值。同时,我们在初始化时,需要遍历一遍整个结构,生成一个打平的数组来保存整个树。当点击某个结点后,使用该数据结构和旧的状态数组生成新的状态数组,再有父节点统一下发各结点状态。
- 对于如何判断结点状态,我们采取下列逻辑:对于某个结点,我们设置checked和halfchecked,他们的初始值分别为true和false。随后遍历其子结点,如果子结点为全选状态,设置其halfchecked为true,否则设置其checked为false。这样遍历完字节点后我们就能得到当前结点的状态checked和halfchecked。checked的优先级高于halfchecked。如果两个都为false证明这个结点是未选中结点。
三、具体实现
有了基本思路以后,我们来具体实现:
首先是对其进行遍历来生成一个初始的数据结构,方便我们后续使用。getDerivedStateFromProps是react16中的一个新的生命周期函数。
//tree.js
static getDerivedStateFromProps(props, state) {
const treeNodes = props.children;
const entities = {
};
for(let i = 0, len = treeNodes.length; i < len; i++)