簡體   English   中英

隨着組件數量的增加反應性能問題

[英]React performance problems with increasing number of components

原來問題出在我的電腦上。 然而,James 提出了一些關於如何隔離問題以及利用 useCallback 和 useMemo 進行優化的好點。

我的反應應用程序的性能存在問題。 現在我將代碼排除在外,因為我覺得可能有一些常識性的答案。

這是演示視頻

這里有一些指示

  • 我沒有不必要的重新渲染。 只有單個組件在懸停時才會被渲染。
  • 動畫被限制在懸停元素的容器 div 中,因此懸停時在該容器外部的頁面上不會發生重新繪制。
  • 我沒有為 hover 效果或檢測使用任何繁重的代碼。

我想知道還有什么可能導致這樣的性能問題。 據我了解,如果它們只是坐在那里而不是重新渲染,那么組件的數量應該無關緊要。

這是正在動畫的卡片組件的代碼。 我不太確定在這里展示什么重要。 顯示所有卡片的父組件不會重新渲染。

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM