高性能分组列表设计-2

通过改变列表项位置更新分组关系#

要解决的问题#

  • 移动分组时,分组所有的子项都要移动,且保持相对位置和关系不变
  • 批量移动未分组列表项到分组内时,相对位置应不变
  • 已分组列表项移动出分组范围时应,应解除分组关系

分析#

当分组移动时,所有分组的子项都不变,首先需要搜索到分组内所有的子项。然后记录该子项在分组内的相对位置,以及在整体列表中的位置。 这样在移动时,方便进行计算。

整体来讲,这个移动过程中的搜索部分将使用深度优先搜索的一种变种。移动后的排序,只需遵守搜索中分组和其子项的相对顺序遍历即可。

分组子项搜索流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if (!groupCache.includes(hasGroup)) {
//组件的分组不在查询的分组内。弹出所有的分组缓存
groupCache = [];
return;
} else {
result.push(item); //组件的分组在分组缓存中

if (hasGroup !== groupCache[groupCache.length - 1]) {
// 如果组件的分组不在缓存的顶层
const hasGroupCacheIndex = groupCache.indexOf(hasGroup);
groupCache = groupCache.slice(0, hasGroupCacheIndex + 1);
}
if (compDatas[item].compCode === 'group') {
// 组件本身是分组组件
groupCache.push(item);
}
}

列表项排序流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

/**
* compDatas 所有的列表项{[code]:{ config: { [groupCode]:groupCode } } }
* topLestSelectComps 和第一个要移动的列表项在同级的组件(处理批量移动的情况),数据结构与compDatas一致
* nearLowBoundsGroup 将要移动列表项所在的分组的下界,数据结构与compDatas一致
*/
if (isToplest) {
//如果移动位置在插入区间的顶部,表明组件在最外层
topLestSelectComps.forEach((item) => {

result[item] = { newGroup: undefined, oldGroup: compDatas[item].config.groupCode };
return (compDatas[item].config.groupCode = undefined);
});

return result;
}
if (nearLowBoundsGroup !== firstCompPrev) {
//如果移动位置的下界的分组code不等于移动组件的code,则解除或更新分组关系
topLestSelectComps.forEach((item) => {
if (item !== nearLowBoundsGroup) {
result[item] = { newGroup: nearLowBoundsGroup, oldGroup: compDatas[item].config.groupCode };
compDatas[item].config.groupCode = nearLowBoundsGroup;
} else {
result[item] = { newGroup: compDatas[item].config.groupCode, oldGroup: compDatas[item].config.groupCode };
}
});
return result;
}
return result;
};

实现#

分组子项查询详细代码
    

    interface GroupConfigStruct {
      groupItemCode: string[];
    }

    interface groupMapValueStruct {
      //分组内组件相对于分组索引的偏移量
      offsetNumer: number;
      //分组的索引
      currentIndex: number;
    }

    /**
    *根据分组关系排序一维数组
    *@param compCodes 所有组件的code
    *@param compDatas 所有组件的数据
    */
    const sortListItem = (compCodes: string[], compDatas: JDV.State['compDatas']) => {
      const groupCodeCache = new Map();
      const result: string[] = [];

      /**
      *递归的回溯当前分组的前驱分组,更新前驱分组的长度偏移量
      *@param groupCode 分组组件的code
      *@param offsetNumber 分组长度的偏移量
      */
      const recursiveBacktracking = (groupCode: string, offsetNumber: number): null => {
        const parentGroupCode = compDatas[groupCode].config.groupCode;
        const belongGroup = groupCodeCache.get(groupCode) as groupMapValueStruct;
        groupCodeCache.set(groupCode, {
          //更新分组缓存,每此插入组件,偏移量+1
          ...belongGroup,
          offsetNumer: belongGroup.offsetNumer + 1,
        });
        if (parentGroupCode) {
          // 如果分组有父分组,回溯一步
          return recursiveBacktracking(parentGroupCode, offsetNumber + 1);
        } else {
          return null;
        }
      };
      compCodes.forEach((item, index) => {
        const group = compDatas[item].config.groupCode ? compDatas[item].config.groupCode : null;
        if (compDatas[item].compCode === 'group') {
          //如果组件是分组组件,将code推入分组缓冲内
          groupCodeCache.set(item, { offsetNumer: 0, currentIndex: index });
        }
        if (group) {
          //在分组内
          if (groupCodeCache.has(group)) {
            // 组件的分组在缓存中
            const belongGroup = groupCodeCache.get(group) as groupMapValueStruct;

            // 分组内组件插入的位置
            const targetIndex = belongGroup.currentIndex + belongGroup.offsetNumer;

            result.splice(targetIndex + 1, 0, item);
            recursiveBacktracking(group, belongGroup.offsetNumer);
          }
        } else {
          result.push(item);
        }
      });
      return result;
    };

    export default sortListItem;
    
  
分组移动后排序详细代码
    
    /**
 * 组件排序时处理分组的逻辑。
 * @param compCodes 所有组件的code
 * @param compDatas 所有组件的数据
 * @param code 当前组件code
 * @param destination 目标位置
 * @returns result {Result} 返回组件排序后的分组关系,用于分组关系变化后,处理分组的尺寸。
 */
export const groupResort = (
  compCodes: string[],
  selectedCompCodes: string[],
  compDatas: JDV.State['compDatas'],
  destination: number
): Result => {
  const isToplest = destination === 0;
  const isBottomlest = destination + 1 === compCodes.length - 1;
  const lowBounds = isBottomlest ? compCodes.length - 1 : destination + 1;
  const interval = compCodes.slice(0, lowBounds); //插入区间
  const intervalLastComp = compDatas[compCodes[lowBounds]];
  const nearLowBoundsGroup = interval.find((item) => intervalLastComp && item === intervalLastComp.config.groupCode); //插入区间最下面的分组段
  const firstCompPrev = compDatas[selectedCompCodes[0]] && compDatas[selectedCompCodes[0]].config.groupCode; // 第一个选中组件的分组
  const topLestSelectComps = selectedCompCodes.filter((item) => compDatas[item].config.groupCode === firstCompPrev); // 和第一个选中在同级的所有选中组件
  const result: Result = {};

  if (isToplest) {
    //如果移动位置在插入区间的顶部,表明组件在最外层
    topLestSelectComps.forEach((item) => {
      result[item] = { newGroup: undefined, oldGroup: compDatas[item].config.groupCode };
      return (compDatas[item].config.groupCode = undefined);
    });

    return result;
  }
  if (nearLowBoundsGroup !== firstCompPrev) {
    //如果移动位置的下界的分组code不等于移动组件的code,则解除或更新分组关系
    topLestSelectComps.forEach((item) => {
      if (item !== nearLowBoundsGroup) {
        result[item] = { newGroup: nearLowBoundsGroup, oldGroup: compDatas[item].config.groupCode };
        compDatas[item].config.groupCode = nearLowBoundsGroup;
      } else {
        result[item] = { newGroup: compDatas[item].config.groupCode, oldGroup: compDatas[item].config.groupCode };
      }
    });
    return result;
  }
  return result;
};

    
  
Author

Ashes Born

Posted on

2021-07-30

Updated on

2024-03-23

Licensed under

Kommentare