[英]React performance problems with increasing number of components
原來問題出在我的電腦上。 然而,James 提出了一些關於如何隔離問題以及利用 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 handleChange = () => {
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.log('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.log('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={handleChange}
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
,但基本上它只是圍繞着您的 function,並且僅在特定值更改時才更新它。 這允許 React 避免在每次渲染時重新創建它並導致 DOM 中更深的組件重新渲染。
您可以在此處閱讀文檔,但其要點是您將編寫getA = useCallback(() => a, [a])
而不是const getA = () => a
,並且該數組包含所有依賴項function 如果更改會導致更新。
確保在 JSX 中使用這些,並避免使用像onClick={(e) => preventDefault(e)}
這樣的箭頭函數。 您調用的preventDefault
甚至可以完全位於組件之外,因為它沒有引用任何特定於組件的內容。
嘗試進行這些更新,看看它是否有所作為。 還要在沒有console.log
的情況下進行測試,因為這也會減慢速度。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.