移动端树形组件的实现

1、前言

这个构想实际上来源于一个全选的组件;即一开始的目的是做一个全选组件,然后在完成的时候被告知可能有多层级列表的情况;由于项目所用的vux组件库没有tree这个组件;于是干脆想着改造一下尝试实现一个tree结构。

2、实现

    2.1、局限和前提

        2.1.1,发现无法在子节点里直接修改节点的props传过来的值了

            解决方案:

                在每一层子节点的data属性里定义一个变量,用于存储深拷贝(props传过来的值)后的值

                深拷贝方法用Object.assign

        2.1.2,由于无法直接在子节点里直接修改props传过来的值,那么会出现以下情况

                1)子节点里的状态发生变更时,得一层一层冒泡到根节点才能去修改当前Tree渲染的数据源里对应的分支节点的数据,因为,对于每一层子节点而言,它的数据源来源于上一节点通过props传过来的数据

                2)深拷贝虽然解决了在子节点的作用域下的一些状态无法发生变更(这句话的意思是,比如当前节点有一个属性控制下一分支列表的显隐,那么当该分支列表的结构层的渲染数据直接取得props传过来的data,由于该data不能修改,那么该属性控制显隐的功能就失效了)

                  但是同时也带来了父节点与子节点的解耦,当父节点的数据源发生变更时,如果不去watch的话,是没办法及时通知子节点发生对应属性的变更的;而vue的生命周期在组件层次只有mounted可以用,而该生命周期在一次会话里只会执行一次,即父节点的变动不会引发子节点的mounted周期;(若是想,似乎可以考虑在父节点引用的子节点出绑定一个key)

                即

                        

                  

    2.2、树组件的具体实现

        2.2.1、前提

            1)下行逻辑:

                对于每一个分支节点而言,它既是上一个节点的子节点,同样也是下一分支树的根节点;对于每一个分支节点而言,如果它被选中了,则其下如果有分支(树),则都会被选中,这是全选的概念

            2)上行逻辑:

                由于每一个分支节点同时是上一节点的子节点,那么意味着,当它被选中时,上一分支节点需要做全选判断(如果该分支下所有子节点都处于选中状态,则该分支节点处于全选状态)

        2.2.2、思路:

                1)选中和取消选中用两个数组来实现(下边将选中的存储数组定义为a, 取消的为b):

                    每一个分支节点在执行选中操作时,往a数组里推进该分支节点,同时判断该分支节点是否存在于b数组里,若存在,则将其从b数组中取出

                    每一个分支节点在执行取消选中时,往b数组里推进该分支节点,同时判断a数组里是否存在该节点,若存在,则取出

                2)自上而下的操作:

                    每一个分支节点在执行选中(取消)时,在将该节点的选中状态改变后,若该节点下存在分支(树),则循环遍历调用每一个子节点的选中操作方法,并将(父节点的)变更后的选中状态传递下去,如图:

                    

                    由于Tree在实现的过程中,本身就是递归调用分支树组件的形式来实现,所以上述的逻辑会层层调用,最终实现自上而下的状态变更

                3)自下而上的操作:

                    由于前提2,每一个分支节点在选中的状态发生变更后,需要将状态更新至其上一层节点,并在上一层节点里触发全选的判断,该判断需要区分取消和选中两种状态,不然会引发整个树的状态紊乱:

                    基于思路1:

                        在父节点里,若触发全选判断的子节点的状态为取消选中, 则该父节点无论之前什么状态,此时应该为取消选中状态,具体实现:


                        上述思路为:若当前父节点下的所有子节点里,只要有一个子节点不存在于选中列表(该列表用于存放所有处于选中状态的节点),则取消该父节点的选中状态;同时需要将其从选中列表中剔除;并且需要冒泡到上层节点

                        若触发全选判断的子节点的状态为选中状态,则需要判断当前取消选中的列表集里是否存在当前父节点下的直属子节点,只要有一个符合条件,意味着当前父节点下存在未选中的节点;否则该父节点处于全选状态,具体实现:

        

                      上述思路为:当该节点下的所有子节点处于选中状态(即不存在于未选中列表集里)时,将该节点的选中状态置为true;同时往选中列表集里推入该父节点;同时向上层冒泡触发上层节点的全选判断

                4)选中/取消选中的相关实现:

                       选中/取消选中需要实现以下逻辑:

                            a)变更当前的选中状态

                            b)(递归)调用下一节点的选中/取消的方法

                            c)根据当前节点的选中状态来决定是推入a数组还是b数组(a、b数组为3.2.2的思路1)里提到的两个集合)

                            d)若当前节点的选中/取消方法是点击触发的,则触发上层冒泡事件(需要判断是点击与否是因为如果不判断,由于逻辑b会递归调用子节点的方法,则最终会形成一次自下而上(至当前节点终止)的冒泡事件调用,而实际上自上而下的过程中就已经决定了在该分支树上的节点判断,所以此时发生在该分支树上的冒泡是多余的

                            具体实现:

                        

3、优化与完善

            实现过程中有个意外的bug:这个bug是这么产生的,由于我在选中的冒泡逻辑里做了如下出来:


            这个逻辑有个致命之处在于最开始的时候如果从下层节点开始选择,则未选中节点列表unKeys一开始是空的,这意味着会出现如下情况:

                1)一个父节点有a、b、c三个节点

                2)当a节点被选中的时候,尽管b和c节点没有被选中,但由于unKeys为空,那么b,c节点顺利绕过了判断

                3)于是checkAll的值成了true

                4)于是开始进行错误的冒泡

                然后,优化后的实现:


你可能感兴趣的:(移动端树形组件的实现)