React useEffect 清理功能不会撤消开发中 React 严格模式引起的更改

问题描述 投票:0回答:1

此问题涉及到 React 在开发过程中故意重新安装组件以查找 bug 的事实。

我知道我可以关闭严格模式,但 React 不推荐这样做。我只是在寻找推荐的方法来解决这个问题。

我正在尝试构建一个打开模态组件,当单击关闭按钮或用户在模态内容之外单击时,该组件会关闭。

因此,为了实现模式的外部单击关闭,我使用了 useEffect 挂钩,我希望其回调函数仅在 isModalPopedUp 状态更改时运行,并且不会在组件的初始渲染时运行。为此,我使用了一个自定义钩子:


import { useRef } from "react";

import { useEffect } from "react";

// This custom hook sets if a useEffect callback runs at initial render or not
export default function useDidMountEffect(argObject = {}) {
    /* 
    argObject = {
        runOnInitialRender: boolean,
        callback: () => void,
        dependencies: any[] // array of dependencies for useEffect
    }
    */
    const didMountRef = useRef(argObject.runOnInitialRender);

    // useEffect to run
    useEffect(() => {
        if (didMountRef.current) {
            // callback will run on first render if argObject.runOnInitialRender is true
            argObject.callback();
        } else {
            // callback will now run on dependencies change
            didMountRef.current = true;
        }
    }, argObject.dependencies); // only run if dependencies change
}

对于模态组件,我将其分为两个组件,处理状态逻辑的父模态:


import { useEffect, useState } from "react";
import Modal from "./modal";
import useDidMountEffect from "./useDidMountHook";
import "./modal.css";

export default function ModalParent() {
    const [isModalPopedUp, setIsModalPopedUp] = useState(false);

    function handleToggleModalPopup() {
        setIsModalPopedUp((prevState) => !prevState);
    }

    function handleOnClose() {
        setIsModalPopedUp(false);
    }

    function handleOutsideModalClick(event) {
        !event.target.classList.contains("modal-content") && handleOnClose();
    }

    // add event listener for outside modal click using the useDidMountEffect hook
    useDidMountEffect({
        runOnInitialRender: false,
        callback: () => {
            // add event listener when modal is shown
            if (isModalPopedUp) {
                document.addEventListener("click", handleOutsideModalClick);
            } else {
                // remove event listener when modal is closed
                document.removeEventListener("click", handleOutsideModalClick);
            }

            // return a cleanup function that removes the event listener when component unmounts
            return () => {
                document.removeEventListener("click", handleOutsideModalClick);
            };
        },
        dependencies: [isModalPopedUp], // only re-run the effect when isModalPopedUp changes
    });

    return (
        <div>
            <button
                onClick={() => {
                    handleToggleModalPopup();
                }}>
                Open Modal Popup
            </button>
            {isModalPopedUp && (
                <Modal
                    header={<h1>Customised Header</h1>}
                    footer={<h1>Customised Footer</h1>}
                    onClose={handleOnClose}
                    body={<div>Customised Body</div>}
                />
            )}
        </div>
    );
}

以及主要模态组件:


export default function Modal({ id, header, body, footer, onClose }) {
    return (
        <div id={id || "modal"} className="modal">
            <div className="modal-content">
                <div className="header">
                    <span onClick={onClose} className="close-modal-icon">
                        &times; {/* an X icon */}
                    </span>
                    <div>{header ? header : "Header"}</div>
                </div>
                <div className="body">
                    {body ? (
                        body
                    ) : (
                        <div>
                            <p>This is our Modal body</p>
                        </div>
                    )}
                </div>
                <div className="footer">
                    {footer ? footer : <h2>footer</h2>}
                </div>
            </div>
        </div>
    );
}

所以,问题是 React 在初始渲染后重新挂载父组件,回调 useDidMountEffect 立即将 click 事件监听器添加到

document
元素,而不会通过“open”将 isModalPopedUp 状态更改为 true模态”按钮单击。

因此,当单击“打开模态”按钮时,isModalPopedUp 会切换为 true,但由于单击事件侦听器过早添加到 document,因此立即更改为 false。所以,最终导致无法通过点击“打开模态”按钮来打开模态。

React.dev 通过使用 useEffect 回调返回的清理函数来撤消重新挂载的更改,提供了一个简单的修复方法:

通常,答案是实现清理功能。 清理函数应该停止或撤消 Effect 正在执行的任何操作。经验法则是,用户不应该能够区分运行一次的效果(如在生产中)和设置→清理→设置序列(如您在开发中看到的)。

我的清理功能从

document
元素中删除了单击事件侦听器,但仍然无法解决问题。

对于模态的样式,我正在使用:


/** @format */

.modal {
    position: fixed;
    z-index: 1;
    padding-top: 2rem;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    overflow: hidden;
    background-color: #b19b9b;
    color: black;
    overflow: auto;
    padding-bottom: 2rem;
}

.modal-content {
    position: relative;
    background-color: #fefefe;
    margin: auto;
    padding: 0;
    border: 1px solid red;
    width: 80%;
    animation-name: animateModal;
    animation-duration: 0.5s;
    animation-timing-function: ease-in-out;
}

.close-modal-icon {
    cursor: pointer;
    font-size: 40px;
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    font-weight: bold;
}

.header {
    padding: 4px 10px;
    background-color: #5cb85c;
    color: white;
}

.body {
    padding: 2px 16px;
    height: 200px;
}

.footer {
    padding: 4px 16px;
    background-color: #5cb85c;
    color: white;
}

@keyframes animateModal {
    from {
        top: -200px;
        opacity: 0;
    }

    to {
        top: 0;
        opacity: 1;
    }
}

reactjs react-hooks web-component
1个回答
0
投票

以下内容将帮助您设置处理模态框之外的点击所需的内容。 const modalRef = useRef(null); const [isModalOpen, setIsModalOpen] = useState(false);

const handleClickOutside = (e: MouseEvent) => {
   if (modalRef.current && !commandBarRef.current.contains(e.target as Node)){
     setIsModalOpen(false);
   }
};

除此之外,您还需要设置引用,并在 useEffect 中设置如下内容:

useEffect(() => {
   if(isModalOpen){
      window.addEventListener('mousedown', handleClickOutside
   }
}, [isModalOpen]);
© www.soinside.com 2019 - 2024. All rights reserved.