useEffect deps在预期中不起作用(与useCallback ref相比)

时间:2020-05-05 10:41:30

标签: javascript reactjs react-hooks

Popular组件呈现Modal,但根据modal.isVisible布尔状态,它将可见。我的目标是附加一个mouseevent侦听器和一个处理程序,以在外部单击该模态时将其关闭。我尝试使用useRef进行操作,并将ref传递给Modal对话框,以便捕获事件并使用event.target.contains检查是否在外部单击了。

问题是,在第一个渲染器上,我不想为文档分配“ mousedown”处理程序,而仅在定义wrapper && wrapper.current并将其置于“模态”对话框之外时才希望。在第一个渲染上,我可以看到效果按预期运行,但是通过设置isVisible -> true扩展Modal时,ref.current应该已更改,但是效果不会再次运行,它将再次运行如果我关闭模态,那么它将正常运行。 ref.current所做的更改不会反映在效果中,即使该效果应该在DOM更新之后运行。为什么呢?

const Modal = ({ isVisible, repo, onClose }) => {
    const wrapper = useRef();
    console.count('render modal');

    const escapeHandler = useCallback(({ key }) => {
        if (key == 'Escape') onClose();
    }, [onClose]);

    useEffect(() => {
        document.addEventListener('keydown', escapeHandler);
        return () => document.removeEventListener('keydown', escapeHandler);
    }, []);

    useEffect(() => {
        // runs after first render, but when setting isVisible to true and causing a rerender
        // the effect doesn't run again despite ref.current is changed to <div>
        // only after closing the Modal with Escape, it will work as expected, why?
        console.count('effect modal');
        console.log(wrapper.current);
    }, [wrapper.current]);

    return !isVisible ? null : (
        <div className="modal">
            <div className="modal-dialog" ref={wrapper}>
                <span className="modal-close" onClick={onClose}>&times;</span>
                {repo && <pre>{JSON.stringify(repo, null, 4)}</pre>}
            </div>
        </div>
    );
};

const Popular = () => {
    const [modal, setModal] = useState({ isVisible: false, repo: null });

    const closeModal = useCallback(() => {
        setModal({ isVisible: false, repo: null });
    }, []);

    return <Modal onClose={closeModal} {...modal} />
};

但是,在阅读了文档之后,如果我使用useCallback并将其作为引用传递,它就可以像这样工作,为什么呢?

const wrapper = useCallback(node => {
    // works as expected every time the ref changes
    console.log(node);
}, []);

让我知道问题措词是否不清楚,我会尽力解释一下

1 个答案:

答案 0 :(得分:2)

我不太确定为什么尽管由于属性更改而在依赖项和组件重新渲染中添加了wrapper.current,但useEffect并不是第一次运行,而是在随后的所有isVisible状态更改中调用。这可能是反应中的错误。也许您可以为此在reactjs上创建问题。


那是

但是您提出的所有其他观察结果都是合理的。每次使用useCallback都会调用该函数,因为这样您可以使用ref回调模式分配ref,其中ref的分配方式类似于ref={node => wrapper.current = node}

这样,每当您的可见状态为true时,都会调用ref回调,如果您像这样使用它,则会导致useCallback函数被调用

const modelRef= useRef(null);
const wrapper = useCallback((node) => {
    modelRef.current = node;
    console.log(node);
}, [])

...

<div ref={wrapper} />

但是,如果您将isVisible作为对useEffect的依赖项而不是wrapper.current

,则上述useEffect代码将正确且确定地运行
useEffect(() => {
    console.count('effect modal');
    console.log(wrapper.current);
}, [isVisible]) 

这也是正确的方法,因为仅当isVisible标志被更改时,您的ref才被更改,并且取决于useEffect的可变值,所以不是一个好的解决方案,因为在某些情况下,当突变不会同时进行重新渲染,在这种情况下,useEffect根本不会运行