perf: avoid uneccessary notes list item rerenders (#1904)

This commit is contained in:
Mo
2022-10-30 10:48:23 -05:00
committed by GitHub
parent 32f03d9470
commit 89927a3790
14 changed files with 208 additions and 68 deletions

View File

@@ -40,6 +40,7 @@ const ContentList: FunctionComponent<Props> = ({
const { selectPreviousItem, selectNextItem } = selectionController
const { hideTags, hideDate, hideNotePreview, hideEditorIcon } = itemListController.webDisplayOptions
const { sortBy } = itemListController.displayOptions
const selectedTag = navigationController.selected
const onScroll: UIEventHandler = useCallback(
(e) => {
@@ -72,25 +73,27 @@ const ContentList: FunctionComponent<Props> = ({
[selectionController],
)
const getTagsForItem = (item: ListableContentItem) => {
if (hideTags) {
return []
}
const getTagsForItem = useCallback(
(item: ListableContentItem) => {
if (hideTags) {
return []
}
const selectedTag = navigationController.selected
if (!selectedTag) {
return []
}
if (!selectedTag) {
return []
}
const tags = application.getItemTags(item)
const tags = application.getItemTags(item)
const isNavigatingOnlyTag = selectedTag instanceof SNTag && tags.length === 1
if (isNavigatingOnlyTag) {
return []
}
const isNavigatingOnlyTag = selectedTag instanceof SNTag && tags.length === 1
if (isNavigatingOnlyTag) {
return []
}
return tags
}
return tags
},
[hideTags, selectedTag, application],
)
return (
<div

View File

@@ -1,8 +1,8 @@
import { ContentType } from '@standardnotes/snjs'
import { FunctionComponent } from 'react'
import React, { FunctionComponent } from 'react'
import FileListItem from './FileListItem'
import NoteListItem from './NoteListItem'
import { AbstractListItemProps } from './Types/AbstractListItemProps'
import { AbstractListItemProps, doListItemPropsMeritRerender } from './Types/AbstractListItemProps'
const ContentListItem: FunctionComponent<AbstractListItemProps> = (props) => {
switch (props.item.content_type) {
@@ -15,4 +15,4 @@ const ContentListItem: FunctionComponent<AbstractListItemProps> = (props) => {
}
}
export default ContentListItem
export default React.memo(ContentListItem, (a, b) => !doListItemPropsMeritRerender(a, b))

View File

@@ -13,6 +13,7 @@ import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
import { useContextMenuEvent } from '@/Hooks/useContextMenuEvent'
import ListItemNotePreviewText from './ListItemNotePreviewText'
import { ListItemTitle } from './ListItemTitle'
import { log, LoggingDomain } from '@/Logging'
const NoteListItem: FunctionComponent<DisplayableListItemProps> = ({
application,
@@ -70,6 +71,8 @@ const NoteListItem: FunctionComponent<DisplayableListItemProps> = ({
useContextMenuEvent(listItemRef, openContextMenu)
log(LoggingDomain.ItemsList, 'Rendering note list item', item.title)
return (
<div
ref={listItemRef}

View File

@@ -1,7 +1,7 @@
import { WebApplication } from '@/Application/Application'
import { FilesController } from '@/Controllers/FilesController'
import { NotesController } from '@/Controllers/NotesController'
import { SortableItem, SNTag } from '@standardnotes/snjs'
import { SortableItem, SNTag, Uuids } from '@standardnotes/snjs'
import { ListableContentItem } from './ListableContentItem'
export type AbstractListItemProps = {
@@ -18,3 +18,89 @@ export type AbstractListItemProps = {
sortBy: keyof SortableItem | undefined
tags: SNTag[]
}
export function doListItemPropsMeritRerender(previous: AbstractListItemProps, next: AbstractListItemProps): boolean {
const simpleComparison: (keyof AbstractListItemProps)[] = [
'onSelect',
'hideDate',
'hideIcon',
'hideTags',
'hidePreview',
'selected',
'sortBy',
]
for (const key of simpleComparison) {
if (previous[key] !== next[key]) {
return true
}
}
if (previous['item'] !== next['item']) {
if (doesItemChangeMeritRerender(previous['item'], next['item'])) {
return true
}
}
return doesTagsChangeMeritRerender(previous['tags'], next['tags'])
}
function doesTagsChangeMeritRerender(previous: SNTag[], next: SNTag[]): boolean {
if (previous === next) {
return false
}
if (previous.length !== next.length) {
return true
}
if (previous.length === 0 && next.length === 0) {
return false
}
if (Uuids(previous).sort().join() !== Uuids(next).sort().join()) {
return true
}
if (
previous
.map((t) => t.title)
.sort()
.join() !==
next
.map((t) => t.title)
.sort()
.join()
) {
return true
}
return false
}
function doesItemChangeMeritRerender(previous: ListableContentItem, next: ListableContentItem): boolean {
if (previous.uuid !== next.uuid) {
return true
}
const propertiesMeritingRerender: (keyof ListableContentItem)[] = [
'title',
'protected',
'updatedAtString',
'createdAtString',
'hidePreview',
'preview_html',
'preview_plain',
'archived',
'starred',
'pinned',
]
for (const key of propertiesMeritingRerender) {
if (previous[key] !== next[key]) {
return true
}
}
return false
}