Оказывается, проблема была в моем компьютере. Однако Джеймс сделал несколько хороших замечаний о том, как изолировать проблему и использовать useCallback и useMemo для оптимизации.
У меня проблемы с производительностью моего реагирующего приложения. На данный момент я исключаю код, потому что чувствую, что могут быть некоторые ответы на здравый смысл.
Вот несколько указателей
Мне интересно, что еще может быть причиной таких проблем с производительностью. Насколько я понимаю, количество компонентов не должно иметь значения, если они просто лежат, а не ререндерятся.
Вот код компонента карточки, который анимируется. Я не совсем был уверен, что здесь важно показать. Родительский компонент, показывающий все карточки, не перерисовывается.
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: обновлен пример видео, чтобы показать повторный рендеринг.
Я думаю, что вы должны сначала попробовать «запомнить» все свои функции с помощью useCallback
. Тем более, что вы передаете некоторые из этих функций другим компонентам, возможно, они вызывают ненужный повторный рендеринг глубже в DOM.
Я не знаю, знакомы ли вы с useCallback
, но в основном он просто обертывает вашу функцию и обновляет ее только при изменении определенных значений. Это позволяет React избежать повторного создания его при каждом рендеринге и повторного рендеринга компонентов, находящихся глубже в DOM.
Вы можете прочитать документы здесь, но суть в том, что вместо const getA = () => a
вы должны написать getA = useCallback(() => a, [a])
, а массив содержит все зависимости для функции, которые вызывают ее обновление в случае изменения.
Убедитесь, что вы используете их в своем JSX, и избегайте стрелочных функций, таких как onClick = {(e) => preventDefault(e)}
. Функция, которую вы вызвали preventDefault
, может даже полностью существовать вне компонента, поскольку она не ссылается ни на что конкретное для компонента.
Попробуйте сделать эти обновления и посмотрите, будет ли это иметь значение. Также проверьте без console.info
, так как это также может замедлить работу.
Итак, я закомментировал все внутри div-оболочки (но оставил dragEvents). Вот так все работает плавно, даже при большом количестве карточек на экране. Вам все еще нужен родительский код в этом контексте? Кроме того, есть ли какие-либо дорогостоящие операции внутри карточного компонента, которые выделяются для вас? Такие вещи, как передача компонентов в качестве реквизита и т. д. Я новичок в React, поэтому я, вероятно, не знаю о множестве вещей, которые мне кажутся нормальными, но могут отрицательно сказываться на производительности. Я также отредактировал видео в своем исходном вопросе, чтобы вы могли видеть вспышки рендеринга. Большое спасибо за изучение этого!
Я только что закончил деактивировать дочерние элементы как по одному, так и группами. Кажется, что каждый ребенок в некоторой степени способствует общему замедлению. Что меня смущает, так это то, что количество компонентов карты на экране оказывает коррелятивное негативное влияние на производительность, когда инструменты разработки показывают, что никакие карты, кроме тех, с которыми взаимодействуют, никогда не перерисовываются. Особенно учитывая, что родитель не меняется и, следовательно, не вызывает никаких изменений в дочерних компонентах.
Здесь нет больших секретов. Не стесняйтесь заглянуть в репозиторий github.com/maxibenner/cardboard. Виновниками являются контейнер «browse» и компонент «CardFile». Вы можете использовать тестовые учетные данные из Readme, чтобы погрузиться прямо в учетную запись с кучей файлов.
Спасибо за наводку, я удалил живой ключ. Думал, что для ключа разработчика риск того стоит, поэтому люди могут сразу запустить его. Вы запускаете его на более быстром компьютере? У меня 5-летний MacBook Pro начального уровня. Не самый быстрый, но я предполагал, что он сможет обрабатывать небольшое приложение React с базовой анимацией CSS со скоростью 60 кадров в секунду.
Я только что понял, что, хотя производительность в Safari может быть немного лучше, в Chrome она определенно намного хуже.
Я только что провел тест на двух других компьютерах, и производительность кажется хорошей. Не знаю, что случилось с моим компьютером. Извините, что потратил ваше время на это. Однако, если вы найдете какую-либо оптимизацию производительности, я буду рад принять ее в качестве ответа.
Большое спасибо, что заглянули в него. При любых других обстоятельствах ваш ответ был бы хорошим способом подойти к проблеме.
На самом деле я не знал об использовании Callback. Я реализовал ваше предложение и обернул все функции. Это немного улучшило производительность, но, к сожалению, ничего серьезного. Что меня смущает, так это то, что производительность каким-то образом связана с количеством элементов карты на странице, когда инструменты разработчика говорят мне, что повторно отображается только та карта, на которую наведен курсор.