perf(files): reuse keys for virtual scrolling

Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
pull/40917/head
John Molakvoæ 2023-10-14 15:52:07 +07:00
parent 16975ae457
commit c4b8abd343
No known key found for this signature in database
GPG Key ID: 60C25B8C072916CF
1 changed files with 35 additions and 6 deletions

@ -16,8 +16,8 @@
:class="gridMode ? 'files-list__tbody--grid' : 'files-list__tbody--list'"
data-cy-files-list-tbody>
<component :is="dataComponent"
v-for="(item, i) in renderedItems"
:key="i"
v-for="({key, item}, i) in renderedItems"
:key="key"
:visible="(i >= bufferItems || index <= bufferItems) && (i < shownItems - bufferItems)"
:source="item"
:index="i"
@ -34,13 +34,18 @@
</template>
<script lang="ts">
import type { File, Folder } from '@nextcloud/files'
import type { File, Folder, Node } from '@nextcloud/files'
import { debounce } from 'debounce'
import Vue, { PropType } from 'vue'
import filesListWidthMixin from '../mixins/filesListWidth.ts'
import logger from '../logger.js'
interface RecycledPoolItem {
key: string,
item: Node,
}
export default Vue.extend({
name: 'VirtualList',
@ -98,7 +103,8 @@ export default Vue.extend({
},
itemHeight() {
// 160px + 44px (name) + 15px (grid gap)
// Align with css in FilesListVirtual
// 138px + 44px (name) + 15px (grid gap)
return this.gridMode ? (160 + 44 + 15) : 56
},
// Grid mode only
@ -128,11 +134,32 @@ export default Vue.extend({
return this.rowCount
},
renderedItems(): (File | Folder)[] {
renderedItems(): RecycledPoolItem[] {
if (!this.isReady) {
return []
}
return this.dataSources.slice(this.startIndex, this.startIndex + this.shownItems)
const items = this.dataSources.slice(this.startIndex, this.startIndex + this.shownItems) as Node[]
const oldItems = items.filter(item => Object.values(this.$_recycledPool).includes(item[this.dataKey]))
const oldItemsKeys = oldItems.map(item => item[this.dataKey] as string)
const unusedKeys = Object.keys(this.$_recycledPool).filter(key => !oldItemsKeys.includes(this.$_recycledPool[key]))
return items.map(item => {
const index = Object.values(this.$_recycledPool).indexOf(item[this.dataKey])
// If defined, let's keep the key
if (index !== -1) {
return {
key: Object.keys(this.$_recycledPool)[index],
item,
}
}
// Get and consume reusable key or generate a new one
const key = unusedKeys.pop() || Math.random().toString(36).substr(2)
this.$_recycledPool[key] = item[this.dataKey]
return { key, item }
})
},
tbodyStyle() {
@ -174,6 +201,8 @@ export default Vue.extend({
if (this.scrollToIndex) {
this.$el.scrollTop = Math.floor((this.index * this.itemHeight) / this.rowCount) + this.beforeHeight
}
this.$_recycledPool = {} as Record<string, any>
},
beforeDestroy() {