Как оптимизировать рендеринг большого списка элементов в MUI
У меня есть код, в основе MUI, который выводит сетку с большим количеством элементов ImageItem, здесь реализованы поиск и смена темы, но работает это крайне медленно при количестве элементов больше ста.
Код сетки:
<pre>import { Grid } from "@mui/material";
import { Box } from "@mui/system";
import React, { useRef } from "react";
import ViewportList from "react-viewport-list";
import AppContext from "../../data/live-data/app-context";
import { LinkImage } from "../../data/types";
import { toClassList } from "../../util/request-util";
import ImageItem from "./image";
type DisplayImg = LinkImage & {
isShow: boolean
}
const Child = ({img, i} : {img: DisplayImg, i: number}) => (
<Grid item xs={1} sx={{
display: img.image && img.isShow ? undefined : 'none'
}} key={i}>
<ImageItem image={img}/>
</Grid>
)
export default function ImageGrid({imgs} : {imgs: LinkImage[] | undefined}) {
const appContext = React.useContext(AppContext)
const columns = appContext.gridColumnsState[0]
const searchQ = appContext.searchQState[0]
imgs = imgs? toClassList(imgs, LinkImage) : undefined
const filteredImgs = (imgs?.map((v) => {
const i = v as DisplayImg
i.isShow = v.getName().includes(searchQ) && !(v.config?.deleted === true || false)
return i
}))
const ref = useRef<HTMLDivElement | null>(null)
return (
<Box sx={{
margin: '10px'
}}>
<Grid columns={columns} spacing={1} container ref={ref} sx= {{paddingBottom: '30px'}}>
{
<ViewportList viewportRef={ref} items={filteredImgs} children= {(img, i) => {return(
<Child i={i} key={i} img={img}/>
)}}/>
}
</Grid>
</Box>
)
}
Код одного элемента:
import { Launch, MoreVert } from "@mui/icons-material";
import { Button, ButtonGroup, ClickAwayListener, IconButton, Paper, styled, useTheme } from "@mui/material";
import React from "react";
import match from 'autosuggest-highlight/match';
import parse from 'autosuggest-highlight/parse';
import AppContext from "../../data/live-data/app-context";
import { Data, LinkImage } from "../../data/types";
import { isValidHttpUrl } from "../../util/request-util";
import EndlessScrollingText from "../util-components/scrolling-text";
import InfoDialog from "./dialogs/info-dialog";
import { onDeletePressed } from "./impl/delete-action";
import EditDialogImage from "./impl/edit-dialog-image";
import ImagePopper from "./poppers/image-popper";
import { LinkConfigWithChange } from "../../data/api/models";
export const StyledIconButton = styled(IconButton)(() => ({
padding: 0,
marginLeft: '5px',
marginRight: '5px'
}))
export default function ImageItem({image} : {image: LinkImage}) {
const ImageState = {
LOADING: 'text.disabled',
ERROR: 'error.main',
OTHER: 'info.main',
OK: 'success.main'
}
const [state, setState] = React.useState<keyof typeof ImageState>('LOADING')
const [code, setCode] = React.useState("unknown")
image.info['Response code'] = code
const checkState = () => {
setState('LOADING')
fetch(image.getLink()).then((r) => {
setCode(r.status.toString())
switch(`${r.status}`[0]) {
case '2':
setState('OK')
break
case '4':
case '5':
setState('ERROR')
break
default:
setState('OTHER')
break
}
}).catch((e) => {
setState('ERROR')
})
}
React.useEffect(() => {
if(image.getNeedCheck()) {
checkState()
} else {
setState('LOADING')
}
}, [image.getLink(), image.getNeedCheck()])
const [editMode, setEditMode] = React.useState(false)
const [infoMode, setInfoMode] = React.useState(false)
const [howered, setHowered] = React.useState(false)
const appContext = React.useContext(AppContext)
const setLastModified = appContext.modifiedTimestampState[1]
const searchQ = appContext.searchQState[0]
const setLastDeleted = appContext.lastDeletedState[1]
const matches = match(image.getName(), searchQ, {insideWords: true})
const parts = parse(image.getName(), matches)
const onDeleteApiCall = (resp: Data<LinkConfigWithChange> | undefined) => {
setLastDeleted(image)
setLastModified((new Date()).getTime())
}
const isErr = state === 'ERROR'
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null)
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(anchorEl ? null : event.currentTarget)
};
const open = Boolean(anchorEl)
const id = open ? 'simple-popper' : undefined
return (
<Paper
onMouseEnter={() => setHowered(true)}
onMouseLeave={() => setHowered(false)}
className={ImageState[state]}
sx={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
transition: 'all 0.3s',
overflow: 'clip',
backgroundColor: ImageState[state],
color: state === 'LOADING' ? 'black' : 'white'
}}>
<Button sx={{
width: '100%',
padding: 0.5,
textTransform: 'none',
':hover': {
textDecoration: 'underline',
backgroundColor: 'transparent'
},
background: 'transparent',
'&.Mui-disabled': {
color: '#DDDDDD'
}
}}
onClick={() => {
if (isValidHttpUrl(image.getLink())) {
window.open(image.getLink(), '_blank')
} else {
window.open(`..${image.getLink()}`, '_blank')
}
}}
disabled={isErr}
disableRipple
disableElevation
variant='text'
color='inherit'
startIcon={isErr ? undefined : <Launch sx={{marginLeft: '5px'}}/>}
>
<EndlessScrollingText text={
parts.map((part, i) => (
<span
key={i}
style={{
fontWeight: part.highlight ? 700 : undefined
}}
>{part.text}</span>
))
} animate={howered} sx={{
width: '100%'
}}/>
</Button>
<ButtonGroup>
<ClickAwayListener onClickAway={() => {
setAnchorEl(null)
}}>
<div>
<StyledIconButton sx={{color: 'inherit'}} aria-describedby={id}
onClick={handleClick}>
<MoreVert />
</StyledIconButton>
{open &&
<ImagePopper
popperOptions={{
placement: 'bottom-start'
}}
image={image}
id={id}
open={open}
anchorEl={anchorEl}
onEditButtonPressed={() => {
setAnchorEl(null)
setEditMode(true)
}}
onInfoButtonPressed={() => {
setAnchorEl(null)
setInfoMode(true)
}}
onDeleteButtonPressed={() => {
setAnchorEl(null)
onDeletePressed({image: image, onReady: onDeleteApiCall })}
}/>
}
</div>
</ClickAwayListener>
</ButtonGroup>
{editMode &&
<EditDialogImage
image={image}
open={editMode}
setOpen={setEditMode}
onClose={() => setEditMode(false)}
/>
}
{infoMode &&
<InfoDialog
image={image}
open={infoMode}
setOpened={setInfoMode}
onClose={() => setInfoMode(false)}
/>
}
</Paper>
)
}
Как можно добиться того, что бы скорость смены темы не зависела от количества элементов (в качестве основы для тем я использую Experimental_CssVarsProvider, поэтому при ее смене меняются толко ко переменные цветов)?