Реагируйте на проблемы с производительностью с увеличением количества компонентов

Оказывается, проблема была в моем компьютере. Однако Джеймс сделал несколько хороших замечаний о том, как изолировать проблему и использовать useCallback и useMemo для оптимизации.

У меня проблемы с производительностью моего реагирующего приложения. На данный момент я исключаю код, потому что чувствую, что могут быть некоторые ответы на здравый смысл.

Это демонстрационное видео

Вот несколько указателей

  • У меня нет ненужных повторных рендеров. При наведении на них отображаются только отдельные компоненты.
  • Анимации ограничены контейнером div зависшего элемента, поэтому при наведении курсора на странице за пределами этого контейнера не происходит перерисовки.
  • Я не использую какой-либо тяжелый код для эффекта наведения или обнаружения.

Мне интересно, что еще может быть причиной таких проблем с производительностью. Насколько я понимаю, количество компонентов не должно иметь значения, если они просто лежат, а не ререндерятся.

Вот код компонента карточки, который анимируется. Я не совсем был уверен, что здесь важно показать. Родительский компонент, показывающий все карточки, не перерисовывается.

export default function CardFile(props) {

    // Input field
    const input = useRef(null)

    //Input state
    const [inputActive, setInputActive] = useState(false);
    const [title, setTitle] = useState(props.file.name)
    const [menuActive, setMenuActive] = useState(false)

    const [draggable, setDraggable] = useState(true)
    const [isDragged, setIsDragged] = useState(false)

    // counter > 0 = is hovered
    const [dragCounter, setDragCounter] = useState(0)
    



    //_________________ FUNCTIONS _________________//
    
    // Handle file delete
    const handleDelete = (e) => {
        firebase.firestore().collection('users').doc(props.file.owner).collection('files').doc(props.file.id).delete().then(() => {
            console.info('Deleted')
        }).catch((err) => console.err(err))
    }

    // Prevent default if necessary
    const preventDefault = (e) => {
        e.preventDefault()
        e.stopPropagation()
    }

    // Handle rename
    const handleRename = (e) => {
        e.stopPropagation()
        setMenuActive(false)
        setInputActive(true)
    }

    // Handle change
    const handleChangeEvent = () => {
        setTitle(input.current.value)
    }

    // Handle focus loss
    const handleFocusLoss = (e) => {
        e.stopPropagation()
        setInputActive(false)
        firebase.firestore().collection('users').doc(props.file.owner).collection('files').doc(props.file.id).update({ name: title })
            .then(() => {
                console.info('Updated title')
            }).catch((err) => console.error(err))
    }

    // Handle title submit
    const handleKeyPress = (e) => {
        console.info('key')
        if (e.code === "Enter") {
            e.preventDefault();

            setInputActive(false)
            firebase.firestore().collection('users').doc(props.file.owner).collection('files').doc(props.file.id).update({ name: title })
                .then(() => {
                    console.info('Submitted title')
                }).catch((err) => console.error(err))
        }
    }

    // Set input focus
    useEffect(() => {
        if (inputActive) {
            input.current.focus()
            input.current.select()
        }
    }, [inputActive])


    //_____________________________DRAGGING___________________________//
    //Handle drag start
    const onDragStartFunctions = () => {
        props.onDragStart(props.file.id)
        setIsDragged(true)
    }
    // Handle drag enter
    const handleDragEnter = (e) => {
        // Only set as target if not equal to source
        if (!isDragged) {
            setDragCounter(dragCounter => dragCounter + 1)
        }
    }
    //Handle drag end
    const handleDragEnd = (e) => {
        e.preventDefault()
        setIsDragged(false)
    }
    // Handle drag exit
    const handleDragLeave = () => {
        // Only remove as target if not equal to source
        if (!isDragged) {
            setDragCounter(dragCounter => dragCounter - 1)
        }
    }
    // Handle drag over
    const handleDragOver = (e) => {
        e.preventDefault()
    }
    // Handle drag drop
    const onDragDropFunctions = (e) => {
        setDragCounter(0)
        // Only trigger when target if not equal to source
        if (!isDragged) {
            props.onDrop({
                id: props.file.id,
                display_type: 'file'
            })
        }
    }



    return (
        <div
            className = {`${styles.card} ${dragCounter !== 0 && styles.is_hovered} ${isDragged && styles.is_dragged}`}
            test = {console.info('render')}
            draggable = {draggable}
            onDragStart = {onDragStartFunctions}
            onDragEnter = {handleDragEnter}
            onDragOver = {handleDragOver}
            onDragEnd = {handleDragEnd}
            onDragLeave = {handleDragLeave}
            onDrop = {onDragDropFunctions}
        >
            <div className = {styles.cardInner}>
                <div className = {styles.videoContainer} onClick = {() => props.handleActiveMedia(props.file, 'show')}>
                    {props.file.thumbnail_url && props.file.type === 'video' &&
                        <MdPlayCircleFilled className = {styles.playButton} />
                    }
                    {!props.file.thumbnail_url && props.file.type === 'image' &&
                        <MdImage className = {styles.processingButton} />
                    }
                    {!props.file.thumbnail_url && props.file.type === 'video' &&
                        <FaVideo className = {styles.processingButton} />
                    }
                    <div className = {styles.image} style = {props.file.thumbnail_url && { backgroundImage: `url(${props.file.thumbnail_url})` }}></div>
                </div>
                <div className = {styles.body}>
                    <div className = {styles.main}>

                        {!inputActive ?
                            <p className = {styles.title}>{title}</p>
                            :
                            <input
                                ref = {input}
                                className = {styles.titleInput}
                                type = "text"
                                onKeyPress = {handleKeyPress}
                                onChange = {handleChangeEvent}
                                onBlur = {handleFocusLoss}
                                defaultValue = {title}
                            />
                        }
                    </div>

                    <ToggleContext onClick = {() => setMenuActive(prevMenuActive => !prevMenuActive)}>
                        {
                            menuActive && <div className = {styles.menuBackground} />
                        }
                        <Dropdown top small active = {menuActive}>
                            <ButtonLight title = {'Rename'} icon = {<MdTitle />} onClick = {handleRename} />
                            <ButtonLight title = {'Label'} icon = {<MdLabel />} onClick = {() => props.handleActiveMedia(props.file, 'label')} />
                            <ButtonLight title = {'Share'} icon = {<MdShare />} onClick = {() => window.alert("Sharing is not yet supported. Stay put.")} />
                            {/*props.file.type === 'video' && <ButtonLight title = {'Split'} icon = {<RiScissorsFill />} />*/}
                            <ButtonLightConfirm
                                danger
                                title = {'Delete'}
                                icon = {<MdDelete />}
                                onClick = {(e) => preventDefault(e)}
                                confirmAction = {handleDelete}
                                preventDrag = {() => setDraggable(false)}
                                enableDrag = {() => setDraggable(true)}
                            />
                        </Dropdown>
                    </ToggleContext>

                </div>
            </div>

        </div>
    );
}

А вот css для анимации:

.is_hovered {
    box-shadow: 0 0 0 3px var(--blue);
}
.is_hovered > div {
    transform: scale(0.9);
    box-shadow: 0 0 5px rgba(0, 0, 0, 0.08);
    transition: .1s;
}

Обновлено: добавлен код

Edit2: обновлен пример видео, чтобы показать повторный рендеринг.

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
0
365
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я думаю, что вы должны сначала попробовать «запомнить» все свои функции с помощью useCallback. Тем более, что вы передаете некоторые из этих функций другим компонентам, возможно, они вызывают ненужный повторный рендеринг глубже в DOM.

Я не знаю, знакомы ли вы с useCallback, но в основном он просто обертывает вашу функцию и обновляет ее только при изменении определенных значений. Это позволяет React избежать повторного создания его при каждом рендеринге и повторного рендеринга компонентов, находящихся глубже в DOM.

Вы можете прочитать документы здесь, но суть в том, что вместо const getA = () => a вы должны написать getA = useCallback(() => a, [a]), а массив содержит все зависимости для функции, которые вызывают ее обновление в случае изменения.

Убедитесь, что вы используете их в своем JSX, и избегайте стрелочных функций, таких как onClick = {(e) => preventDefault(e)}. Функция, которую вы вызвали preventDefault, может даже полностью существовать вне компонента, поскольку она не ссылается ни на что конкретное для компонента.

Попробуйте сделать эти обновления и посмотрите, будет ли это иметь значение. Также проверьте без console.info, так как это также может замедлить работу.

На самом деле я не знал об использовании Callback. Я реализовал ваше предложение и обернул все функции. Это немного улучшило производительность, но, к сожалению, ничего серьезного. Что меня смущает, так это то, что производительность каким-то образом связана с количеством элементов карты на странице, когда инструменты разработчика говорят мне, что повторно отображается только та карта, на которую наведен курсор.

Max 28.12.2020 11:28

Итак, я закомментировал все внутри div-оболочки (но оставил dragEvents). Вот так все работает плавно, даже при большом количестве карточек на экране. Вам все еще нужен родительский код в этом контексте? Кроме того, есть ли какие-либо дорогостоящие операции внутри карточного компонента, которые выделяются для вас? Такие вещи, как передача компонентов в качестве реквизита и т. д. Я новичок в React, поэтому я, вероятно, не знаю о множестве вещей, которые мне кажутся нормальными, но могут отрицательно сказываться на производительности. Я также отредактировал видео в своем исходном вопросе, чтобы вы могли видеть вспышки рендеринга. Большое спасибо за изучение этого!

Max 28.12.2020 11:51

Я только что закончил деактивировать дочерние элементы как по одному, так и группами. Кажется, что каждый ребенок в некоторой степени способствует общему замедлению. Что меня смущает, так это то, что количество компонентов карты на экране оказывает коррелятивное негативное влияние на производительность, когда инструменты разработки показывают, что никакие карты, кроме тех, с которыми взаимодействуют, никогда не перерисовываются. Особенно учитывая, что родитель не меняется и, следовательно, не вызывает никаких изменений в дочерних компонентах.

Max 28.12.2020 16:39

Здесь нет больших секретов. Не стесняйтесь заглянуть в репозиторий github.com/maxibenner/cardboard. Виновниками являются контейнер «browse» и компонент «CardFile». Вы можете использовать тестовые учетные данные из Readme, чтобы погрузиться прямо в учетную запись с кучей файлов.

Max 28.12.2020 18:28

Спасибо за наводку, я удалил живой ключ. Думал, что для ключа разработчика риск того стоит, поэтому люди могут сразу запустить его. Вы запускаете его на более быстром компьютере? У меня 5-летний MacBook Pro начального уровня. Не самый быстрый, но я предполагал, что он сможет обрабатывать небольшое приложение React с базовой анимацией CSS со скоростью 60 кадров в секунду.

Max 28.12.2020 19:44

Я только что понял, что, хотя производительность в Safari может быть немного лучше, в Chrome она определенно намного хуже.

Max 28.12.2020 20:19

Я только что провел тест на двух других компьютерах, и производительность кажется хорошей. Не знаю, что случилось с моим компьютером. Извините, что потратил ваше время на это. Однако, если вы найдете какую-либо оптимизацию производительности, я буду рад принять ее в качестве ответа.

Max 28.12.2020 20:35

Большое спасибо, что заглянули в него. При любых других обстоятельствах ваш ответ был бы хорошим способом подойти к проблеме.

Max 28.12.2020 21:32

Другие вопросы по теме