Как оптимизировать рендеринг большого списка элементов в 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, поэтому при ее смене меняются толко ко переменные цветов)?


Ответы (0 шт):