Skip to content

Commit dd893ba

Browse files
committed
chore: Optimize json flat performance.
1 parent 00fd3f1 commit dd893ba

File tree

4 files changed

+120
-96
lines changed

4 files changed

+120
-96
lines changed

src/components/Tree/index.vue

Lines changed: 82 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -5,38 +5,47 @@
55
'vjs-tree': true,
66
'is-virtual': virtual,
77
}"
8-
@scroll="onTreeScroll"
8+
@scroll="virtual ? onTreeScroll() : undefined"
99
>
10-
<div :style="virtual && { height: `${flatData.length * itemHeight}px` }">
11-
<div :style="virtual && { transform: `translateY(${translateY}px)` }">
12-
<tree-node
13-
v-for="item in visibleData"
14-
:key="item.id"
15-
:node="item"
16-
:collapsed="!!hiddenPaths[item.path]"
17-
:custom-value-formatter="customValueFormatter"
18-
:show-double-quotes="showDoubleQuotes"
19-
:show-length="showLength"
20-
:collapsed-on-click-brackets="collapsedOnClickBrackets"
21-
:checked="selectedPaths.includes(item.path)"
22-
:selectable-type="selectableType"
23-
:show-line="showLine"
24-
:show-select-controller="showSelectController"
25-
:select-on-click-node="selectOnClickNode"
26-
:path-selectable="pathSelectable"
27-
:highlight-selected-node="highlightSelectedNode"
28-
@tree-node-click="onTreeNodeClick"
29-
@brackets-click="onBracketsClick"
30-
@selected-change="onSelectedChange"
31-
/>
10+
<div class="vjs-tree-list" :style="virtual && { height: `${height}px` }">
11+
<div
12+
class="vjs-tree-list__holder"
13+
:style="virtual && { height: `${flatData.length * itemHeight}px` }"
14+
>
15+
<div
16+
class="vjs-tree-list__holder-inner"
17+
:style="virtual && { transform: `translateY(${translateY}px)` }"
18+
>
19+
<tree-node
20+
v-for="item in visibleData"
21+
:key="item.id"
22+
:node="item"
23+
:collapsed="!!hiddenPaths[item.path]"
24+
:custom-value-formatter="customValueFormatter"
25+
:show-double-quotes="showDoubleQuotes"
26+
:show-length="showLength"
27+
:collapsed-on-click-brackets="collapsedOnClickBrackets"
28+
:checked="selectedPaths.includes(item.path)"
29+
:selectable-type="selectableType"
30+
:show-line="showLine"
31+
:show-select-controller="showSelectController"
32+
:select-on-click-node="selectOnClickNode"
33+
:path-selectable="pathSelectable"
34+
:highlight-selected-node="highlightSelectedNode"
35+
@tree-node-click="onTreeNodeClick"
36+
@brackets-click="onBracketsClick"
37+
@selected-change="onSelectedChange"
38+
:style="itemHeight && itemHeight !== 20 ? { lineHeight: `${itemHeight}px` } : {}"
39+
/>
40+
</div>
3241
</div>
3342
</div>
3443
</div>
3544
</template>
3645

3746
<script>
3847
import TreeNode from 'src/components/TreeNode';
39-
import { getDataType, jsonFlatten } from 'src/utils';
48+
import { jsonFlatten } from 'src/utils';
4049
import './styles.less';
4150
4251
export default {
@@ -45,12 +54,11 @@ export default {
4554
TreeNode,
4655
},
4756
props: {
48-
// 当前树的数据
57+
// JSON
4958
data: {
5059
type: [String, Number, Boolean, Array, Object],
5160
default: null,
5261
},
53-
// 定义树的深度, 大于该深度的子树将不被展开
5462
deep: {
5563
type: Number,
5664
default: Infinity,
@@ -59,7 +67,7 @@ export default {
5967
type: Boolean,
6068
default: false,
6169
},
62-
// 数据层级顶级路径
70+
// define root path
6371
path: {
6472
type: String,
6573
default: 'root',
@@ -68,26 +76,31 @@ export default {
6876
type: Boolean,
6977
default: false,
7078
},
79+
// When using virtual scroll, set the height of tree.
80+
height: {
81+
type: Number,
82+
default: 400,
83+
},
84+
// When using virtual scroll, define the height of each row.
7185
itemHeight: {
7286
type: Number,
7387
default: 20,
7488
},
75-
// 是否显示数组|对象的长度
7689
showLength: {
7790
type: Boolean,
7891
default: false,
7992
},
80-
// key名是否显示双引号
93+
// showDoubleQuotes on key
8194
showDoubleQuotes: {
8295
type: Boolean,
8396
default: true,
8497
},
85-
// 定义数据层级支持的选中方式, 默认无该功能
98+
// Define the selection method supported by the data level, which is not available by default.
8699
selectableType: {
87100
type: String,
88101
default: '', // ''|multiple|single
89102
},
90-
// 是否展示左侧选择控件
103+
// Whether to display the selection control.
91104
showSelectController: {
92105
type: Boolean,
93106
default: false,
@@ -96,18 +109,18 @@ export default {
96109
type: Boolean,
97110
default: true,
98111
},
99-
// 是否在点击树的时候选中节点
112+
// Whether to trigger selection when clicking on the node.
100113
selectOnClickNode: {
101114
type: Boolean,
102115
default: true,
103116
},
104-
// 存在选择功能时, 定义已选中的数据层级
105-
// 多选时为数组['root.a', 'root.b'], 单选时为字符串'root.a'
117+
// When there is a selection function, define the selected path.
118+
// For multiple selections, it is an array ['root.a','root.b'], for single selection, it is a string of 'root.a'.
106119
value: {
107120
type: [Array, String],
108121
default: () => '',
109122
},
110-
// 定义某个数据层级是否支持选中操作
123+
// When using the selectableType, define whether current path/content is enabled.
111124
pathSelectable: {
112125
type: Function,
113126
default: () => true,
@@ -127,11 +140,6 @@ export default {
127140
type: Function,
128141
default: null,
129142
},
130-
// Number of lines to show when virtual is true
131-
virtualLines: {
132-
type: Number,
133-
default: 10
134-
}
135143
},
136144
data() {
137145
return {
@@ -141,10 +149,7 @@ export default {
141149
const depthComparison = this.deepCollapseChildren
142150
? item.level >= this.deep
143151
: item.level === this.deep;
144-
if (
145-
(item.type === 'objectStart' || item.type === 'arrayStart') &&
146-
depthComparison
147-
) {
152+
if ((item.type === 'objectStart' || item.type === 'arrayStart') && depthComparison) {
148153
return {
149154
...acc,
150155
[item.path]: 1,
@@ -155,31 +160,41 @@ export default {
155160
};
156161
},
157162
computed: {
158-
flatData() {
163+
originFlatData() {
164+
return jsonFlatten(this.data, this.path);
165+
},
166+
167+
flatData({ originFlatData, hiddenPaths }) {
168+
// Avoid accessing `this` in a loop to improve performance
159169
let startHiddenItem = null;
160-
const data = jsonFlatten(this.data, this.path).reduce((acc, cur, index) => {
170+
const data = [];
171+
const length = originFlatData.length;
172+
for (let i = 0; i < length; i++) {
173+
const cur = originFlatData[i];
161174
const item = {
162175
...cur,
163-
id: index,
176+
id: i,
164177
};
165-
const isHidden = this.hiddenPaths[item.path];
178+
const isHidden = hiddenPaths[item.path];
166179
if (startHiddenItem && startHiddenItem.path === item.path) {
167180
const isObject = startHiddenItem.type === 'objectStart';
168181
const mergeItem = {
169-
...startHiddenItem,
170182
...item,
183+
...startHiddenItem,
184+
showComma: item.showComma,
171185
content: isObject ? '{...}' : '[...]',
172186
type: isObject ? 'objectCollapsed' : 'arrayCollapsed',
173187
};
174188
startHiddenItem = null;
175-
return acc.concat(mergeItem);
189+
data.push(mergeItem);
176190
} else if (isHidden && !startHiddenItem) {
177191
startHiddenItem = item;
178-
return acc;
192+
continue;
193+
} else {
194+
if (startHiddenItem) continue;
195+
else data.push(item);
179196
}
180-
181-
return startHiddenItem ? acc : acc.concat(item);
182-
}, []);
197+
}
183198
return data;
184199
},
185200
@@ -213,39 +228,43 @@ export default {
213228
},
214229
215230
flatData: {
216-
handler() {
217-
this.onTreeScroll();
231+
handler(val) {
232+
this.updateVisibleData(val);
218233
},
219234
immediate: true,
220235
},
221236
},
222237
methods: {
223-
onTreeScroll() {
238+
updateVisibleData(flatDataValue) {
224239
if (this.virtual) {
225-
const visibleCount = this.virtualLines;
240+
const visibleCount = this.height / this.itemHeight;
226241
const scrollTop = (this.$refs.tree && this.$refs.tree.scrollTop) || 0;
227242
const scrollCount = Math.floor(scrollTop / this.itemHeight);
228243
let start =
229244
scrollCount < 0
230245
? 0
231-
: scrollCount + visibleCount > this.flatData.length
232-
? this.flatData.length - visibleCount
246+
: scrollCount + visibleCount > flatDataValue.length
247+
? flatDataValue.length - visibleCount
233248
: scrollCount;
234249
if (start < 0) {
235250
start = 0;
236251
}
237252
const end = start + visibleCount;
238253
this.translateY = start * this.itemHeight;
239-
this.visibleData = this.flatData.filter((item, index) => index >= start && index < end);
254+
this.visibleData = flatDataValue.filter((item, index) => index >= start && index < end);
240255
} else {
241-
this.visibleData = this.flatData;
256+
this.visibleData = flatDataValue;
242257
}
243258
},
244259
260+
onTreeScroll() {
261+
this.updateVisibleData(this.flatData);
262+
},
263+
245264
onSelectedChange({ path }) {
246265
const type = this.selectableType;
247266
if (type === 'multiple') {
248-
const index = this.selectedPaths.findIndex(item => item === path);
267+
const index = this.selectedPaths.findIndex((item) => item === path);
249268
const oldVal = [...this.selectedPaths];
250269
if (index !== -1) {
251270
this.selectedPaths.splice(index, 1);

src/components/TreeNode/styles.less

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
.@{css-prefix}-tree__node {
88
display: flex;
99
position: relative;
10+
line-height: 20px;
1011

1112
&.has-selector {
12-
padding-left: @selector-span;
13+
padding-left: 30px;
1314
}
1415

1516
&.is-highlight,

src/themes.less

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,3 @@
2121

2222
/* common border-color */
2323
@border-color: #bfcbd9;
24-
25-
/* 左侧可选区域占用空间 */
26-
@selector-span: 30px;

src/utils/index.js

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,16 @@ export function jsonFlatten(
1111
) {
1212
const dataType = getDataType(data);
1313
if (dataType === 'array') {
14-
const inner = data
15-
.map((item, idx, arr) =>
14+
const inner = arrFlat(
15+
data.map((item, idx, arr) =>
1616
jsonFlatten(item, `${path}[${idx}]`, level + 1, {
1717
index: idx,
1818
showComma: idx !== arr.length - 1,
1919
length,
2020
type,
2121
}),
22-
)
23-
// No flat, for compatibility.
24-
.reduce((acc, val) => acc.concat(val), []);
22+
),
23+
);
2524
return [
2625
jsonFlatten('[', path, level, { key, length: data.length, type: 'arrayStart' })[0],
2726
].concat(
@@ -30,8 +29,8 @@ export function jsonFlatten(
3029
);
3130
} else if (dataType === 'object') {
3231
const keys = Object.keys(data);
33-
const inner = keys
34-
.map((objKey, idx, arr) =>
32+
const inner = arrFlat(
33+
keys.map((objKey, idx, arr) =>
3534
jsonFlatten(
3635
data[objKey],
3736
objKey.includes('.') ? `${path}["${objKey}"]` : `${path}.${objKey}`,
@@ -43,9 +42,8 @@ export function jsonFlatten(
4342
type,
4443
},
4544
),
46-
)
47-
// No flat, for compatibility.
48-
.reduce((acc, val) => acc.concat(val), []);
45+
),
46+
);
4947
return [
5048
jsonFlatten('{', path, level, { key, index, length: keys.length, type: 'objectStart' })[0],
5149
].concat(
@@ -54,24 +52,33 @@ export function jsonFlatten(
5452
);
5553
}
5654

57-
const output = Object.entries({
58-
content: data,
59-
level,
60-
key,
61-
index,
62-
path,
63-
showComma,
64-
length,
65-
type,
66-
}).reduce((acc, [key, value]) => {
67-
if (value !== undefined) {
68-
return {
69-
...acc,
70-
[key]: value,
71-
};
72-
}
73-
return acc;
74-
}, {});
55+
return [
56+
{
57+
content: data,
58+
level,
59+
key,
60+
index,
61+
path,
62+
showComma,
63+
length,
64+
type,
65+
},
66+
];
67+
}
7568

76-
return getDataType(output) === 'object' ? [output] : output;
69+
export function arrFlat(arr) {
70+
if (typeof Array.prototype.flat === 'function') {
71+
return arr.flat();
72+
}
73+
const stack = [...arr];
74+
const result = [];
75+
while (stack.length) {
76+
const first = stack.shift();
77+
if (Array.isArray(first)) {
78+
stack.unshift(...first);
79+
} else {
80+
result.push(first);
81+
}
82+
}
83+
return result;
7784
}

0 commit comments

Comments
 (0)