Skip to content

Commit bc5928e

Browse files
committed
perf: virtual list
1 parent 9bd52d4 commit bc5928e

File tree

4 files changed

+52
-31
lines changed

4 files changed

+52
-31
lines changed

components/select/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ const Select = defineComponent({
6161
inheritAttrs: false,
6262
props: initDefaultProps(selectProps(), {
6363
listHeight: 256,
64-
listItemHeight: 24,
64+
listItemHeight: 32,
6565
}),
6666
SECRET_COMBOBOX_MODE_DO_NOT_USE,
6767
// emits: ['change', 'update:value', 'blur'],

components/vc-virtual-list/List.tsx

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import type { PropType, Component, CSSProperties } from 'vue';
22
import {
3+
shallowRef,
4+
toRaw,
35
onMounted,
46
onUpdated,
57
ref,
@@ -113,20 +115,36 @@ const List = defineComponent({
113115
scrollTop: 0,
114116
scrollMoving: false,
115117
});
116-
117-
const mergedData = computed(() => {
118+
const data = computed(() => {
118119
return props.data || EMPTY_DATA;
119120
});
120-
121+
const mergedData = shallowRef([]);
122+
watch(
123+
data,
124+
() => {
125+
mergedData.value = toRaw(data.value).slice();
126+
},
127+
{ immediate: true },
128+
);
129+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
130+
const itemKey = shallowRef((_item: Record<string, any>) => undefined);
131+
watch(
132+
() => props.itemKey,
133+
val => {
134+
if (typeof val === 'function') {
135+
itemKey.value = val;
136+
} else {
137+
itemKey.value = item => item?.[val];
138+
}
139+
},
140+
{ immediate: true },
141+
);
121142
const componentRef = ref<HTMLDivElement>();
122143
const fillerInnerRef = ref<HTMLDivElement>();
123144
const scrollBarRef = ref<any>(); // Hack on scrollbar to enable flash call
124145
// =============================== Item Key ===============================
125146
const getKey = (item: Record<string, any>) => {
126-
if (typeof props.itemKey === 'function') {
127-
return props.itemKey(item);
128-
}
129-
return item?.[props.itemKey];
147+
return itemKey.value(item);
130148
};
131149

132150
const sharedConfig = {
@@ -217,7 +235,6 @@ const List = defineComponent({
217235
() => state.scrollTop,
218236
mergedData,
219237
updatedMark,
220-
heights,
221238
() => props.height,
222239
offsetHeight,
223240
],
@@ -232,21 +249,27 @@ const List = defineComponent({
232249
let endIndex: number | undefined;
233250
const dataLen = mergedData.value.length;
234251
const data = mergedData.value;
252+
const scrollTop = state.scrollTop;
253+
const { itemHeight, height } = props;
254+
const scrollTopHeight = scrollTop + height;
255+
235256
for (let i = 0; i < dataLen; i += 1) {
236257
const item = data[i];
237258
const key = getKey(item);
238259

239-
const cacheHeight = heights.value[key];
240-
const currentItemBottom =
241-
itemTop + (cacheHeight === undefined ? props.itemHeight! : cacheHeight);
260+
let cacheHeight = heights.get(key);
261+
if (cacheHeight === undefined) {
262+
cacheHeight = itemHeight;
263+
}
264+
const currentItemBottom = itemTop + cacheHeight;
242265

243-
if (currentItemBottom >= state.scrollTop && startIndex === undefined) {
266+
if (startIndex === undefined && currentItemBottom >= scrollTop) {
244267
startIndex = i;
245268
startOffset = itemTop;
246269
}
247270

248271
// Check item bottom in the range. We will render additional one item for motion usage
249-
if (currentItemBottom > state.scrollTop + props.height! && endIndex === undefined) {
272+
if (endIndex === undefined && currentItemBottom > scrollTopHeight) {
250273
endIndex = i;
251274
}
252275

components/vc-virtual-list/hooks/useHeights.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1-
import type { VNodeProps, ComputedRef, Ref } from 'vue';
2-
import { shallowRef, watch, ref } from 'vue';
1+
import type { VNodeProps, Ref, ShallowRef } from 'vue';
2+
import { watch, ref } from 'vue';
33
import type { GetKey } from '../interface';
44

5-
type CacheMap = Ref<Record<string, number>>;
5+
export type CacheMap = Map<any, number>;
66

77
export default function useHeights<T>(
8-
mergedData: ComputedRef<any[]>,
8+
mergedData: ShallowRef<any[]>,
99
getKey: GetKey<T>,
1010
onItemAdd?: ((item: T) => void) | null,
1111
onItemRemove?: ((item: T) => void) | null,
1212
): [(item: T, instance: HTMLElement) => void, () => void, CacheMap, Ref<Symbol>] {
1313
const instance = new Map<VNodeProps['key'], HTMLElement>();
14-
const heights = shallowRef({});
14+
let heights = new Map();
1515
const updatedMark = ref(Symbol('update'));
1616
watch(mergedData, () => {
17-
heights.value = {};
17+
heights = new Map();
1818
updatedMark.value = Symbol('update');
1919
});
2020
let heightUpdateId = 0;
@@ -28,10 +28,10 @@ export default function useHeights<T>(
2828
instance.forEach((element, key) => {
2929
if (element && element.offsetParent) {
3030
const { offsetHeight } = element;
31-
if (heights.value[key!] !== offsetHeight) {
31+
if (heights.get(key) !== offsetHeight) {
3232
//changed = true;
3333
updatedMark.value = Symbol('update');
34-
heights.value[key!] = element.offsetHeight;
34+
heights.set(key, element.offsetHeight);
3535
}
3636
}
3737
});

components/vc-virtual-list/hooks/useScrollTo.tsx

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import type { Data } from '../../_util/type';
2-
import type { ComputedRef, Ref } from 'vue';
1+
import type { ShallowRef, Ref } from 'vue';
32
import raf from '../../_util/raf';
43
import type { GetKey } from '../interface';
4+
import type { CacheMap } from './useHeights';
55

66
export default function useScrollTo(
77
containerRef: Ref<Element | undefined>,
8-
mergedData: ComputedRef<any[]>,
9-
heights: Ref<Data>,
8+
mergedData: ShallowRef<any[]>,
9+
heights: CacheMap,
1010
props,
1111
getKey: GetKey,
1212
collectHeight: () => void,
@@ -58,11 +58,10 @@ export default function useScrollTo(
5858
let itemBottom = 0;
5959

6060
const maxLen = Math.min(data.length, index);
61-
6261
for (let i = 0; i <= maxLen; i += 1) {
6362
const key = getKey(data[i]);
6463
itemTop = stackTop;
65-
const cacheHeight = heights.value[key!];
64+
const cacheHeight = heights.get(key);
6665
itemBottom = itemTop + (cacheHeight === undefined ? itemHeight : cacheHeight);
6766

6867
stackTop = itemBottom;
@@ -71,7 +70,7 @@ export default function useScrollTo(
7170
needCollectHeight = true;
7271
}
7372
}
74-
73+
const scrollTop = containerRef.value.scrollTop;
7574
// Scroll to
7675
let targetTop: number | null = null;
7776

@@ -84,7 +83,6 @@ export default function useScrollTo(
8483
break;
8584

8685
default: {
87-
const { scrollTop } = containerRef.value;
8886
const scrollBottom = scrollTop + height;
8987
if (itemTop < scrollTop) {
9088
newTargetAlign = 'top';
@@ -94,7 +92,7 @@ export default function useScrollTo(
9492
}
9593
}
9694

97-
if (targetTop !== null && targetTop !== containerRef.value.scrollTop) {
95+
if (targetTop !== null && targetTop !== scrollTop) {
9896
syncScrollTop(targetTop);
9997
}
10098
}

0 commit comments

Comments
 (0)