高性能分组列表设计(1)

高性能分组列表设计#

整体目标#

  1. 分组存在嵌套关系,且深度无理论上限
  2. 可以通过拖拽,将已分组元素拖出,接触分组关系
  3. 可以通过拖拽,将未分组元素拖入分组内,建立新的分组关系
  4. 未分组列表项移动时,会自动越过分组及其子组件
  5. 未分组列表项进行分组时,应保持分组前的相对顺序
  6. 已分组列表项,在解除分组时,应保持分组前的相对顺序
  7. 以上操作对直接操作分组时,也应有效(这里将分组也作为一个列表项进行操作) ## 分析
  • 由于目标1&5,数据结构应保持一维结构,即对象数组的形式。这样的数据结构,提供了列表项的基础顺序,方便在创建分组时保持列表项的相对顺序。
  • 对于目标2&3&5&6,在计算拖拽项是否建立/更新/删除分组关系时,应记录已分组列表项在组内的相对位置,方便在分组关系变化时,对列表的位置进行排序
  • 对于目标7,应把分组也作为列表项之一。提供“type”字段作为分组列表项和其他列表的区别,为后续可能拓展分组的展开/收起功能
  • 渲染列表时会使用一个多维结构的数据,方便递归的对列表进行渲染,对于jsx语法友好。 ## 数据结构设计

列表项数据结构

1
2
3
4
interface ListItem {
code: string;
groupCode: string;
}

列表数据结构

1
type List = ListImte[]

更新分组时的辅助数据结构

1
2
3
4
5
type GroupStack = {
groupCode: string;
index: number; // 分组真实的下标
offsetNumber: number // 分组的长度,方便记录分组内列表项的相对位置
}[]

用于react渲染的数据结构

1
2
3
4
5
interface AssistStruct {
code: string;
children?: AssistStruct[];
parentGroupCode?: string; //pop stack flag
}

算法选择#

一维对象数组转换成嵌套结构设计:#

检测分组闭合,算法属于括号闭合算法的变种。

使用栈记录,未闭合的分组code。当前列表项中的group-code字段与栈顶的code不相等时,表示分组闭合,并且弹出当前栈顶元素。

具体实现#

一维对象数组转换成嵌套结构实现:#

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/**
* 将一维数组转成多层结构
* @param compCodes 所有组件的code
* @param compDatas 所有组件的数据
* @returns 返回和code相关的嵌套结构、
*/
const subList = (compCodes: string[], compDatas: JDV.State['compDatas']): AssistStruct[] => {
let groupStack: GroupStack[] = [];
const resultData: AssistStruct[] = [];

const stackPop = (groupCode?: string) => {
let len = groupStack.length - 1;
while (len >= 0) {
if (groupStack[len].groupCode !== groupCode) {
groupStack.pop();
} else {
break;
}
len--;
}
};

const setResult = (result: AssistStruct[], groupStack: GroupStack[], groupCode: string, value: AssistStruct) => {
groupStack.forEach((item, index) => {
if (!result) {
return null;
}
if (!result[item.index]) {
return;
}
if (result[item.index].code !== groupCode) {
// 如果当前组件的分组不等于结果中的key,向下搜索
return setResult(result[item.index].children as AssistStruct[], groupStack.slice(index + 1), groupCode, value);
} else {
if (result[item.index].children) {
(result[item.index].children as AssistStruct[]).push(value);
item.offsetNumber += 1;
} else {
result[item.index].children = [value];
}
}
});
};

compCodes.forEach((item, index) => {
const hasGroup = compDatas[item] ? compDatas[item].config.groupCode : undefined;
stackPop(hasGroup);
if (compDatas[item].compCode === 'group') {
if (hasGroup) {
// 如果当前组件的父组件在栈顶,更新结果树
setResult(resultData, groupStack.slice(0), hasGroup, {
code: item,
children: [],
});

//如果当前分组有父分组,此时分组栈一定不为空,分组索引为父分组长度-1
// debugger;
groupStack.push({
groupCode: item,
index: groupStack.length ? groupStack[groupStack.length - 1].offsetNumber - 1 : index,
offsetNumber: 0,
});
} else {
groupStack = []; //没有分组,清空栈
resultData.push({
code: item,
children: [],
});
//如果当前分组没有父分组,此时分组栈一定为空,分组索引为结果长度
groupStack.push({
groupCode: item,
index: resultData.length - 1,
offsetNumber: 0,
});
}
} else {
if (hasGroup) {
// 如果当前组件的父组件在栈顶,更新结果树
setResult(resultData, groupStack.slice(0), hasGroup, {
code: item,
});
} else {
groupStack = []; //没有分组,清空栈
resultData.push({
code: item,
});
}
}
});
return resultData;
Author

Ashes Born

Posted on

2021-07-28

Updated on

2024-03-23

Licensed under

Kommentare