ref.current в useEffect deps не работает, когда ожидалось (по сравнению с useCallback ref) - PullRequest
2 голосов
/ 05 мая 2020

Популярный компонент отображает модальное окно, но он будет виден в зависимости от логического состояния modal.isVisible. Моя цель - прикрепить прослушиватель событий мыши и обработчик, чтобы закрыть модальное окно при нажатии вне его. Я попытался сделать это с помощью useRef и передать ссылку в модальное диалоговое окно, чтобы захватить событие и проверить, был ли нажат снаружи, используя event.target.contains.

Проблема в том, что при первом рендеринге я не хочу чтобы назначить обработчик mousedown документу, но только если wrapper && wrapper.current определен и находится вне модального диалогового окна. при первом рендеринге я вижу, что эффект работает, как ожидалось, но при расширении модального окна, установив isVisible -> true, ref.current должен был измениться, но эффект больше не запускается, он снова запустится, если я закрою Modal, и тогда он будет работать как expecetd. Изменения ref.current не отражаются в эффекте, даже если эффект должен запускаться после обновления DOM. Почему?

<code>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)}
} ); }; const Popular = () => {const [модальный, setModal] = useState ({isVisible: false, repo: null}); const closeModal = useCallback (() => {setModal ({isVisible: false, repo: null});}, []); возвращение };

Однако, после прочтения документации, если я использую useCallback и передаю его в качестве ссылки, он работает, как указано, вот так, почему?

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

Сообщите мне если формулировка вопроса немного непонятна, попробую пояснить чуть получше

1 Ответ

1 голос
/ 05 мая 2020

Я не совсем уверен, почему, несмотря на добавление wrapper.current к зависимости и повторному рендерингу компонента из-за изменения свойства, useEffect не запускается в первый раз, а вызывается при всех последующих изменениях состояния isVisible. Это может быть ошибка в реакции. Возможно, вы можете создать для этого проблему на reactjs.


Тем не менее,

Однако все остальные ваши наблюдения оправданы. Использование useCallback вызывает функцию каждый раз, потому что таким образом вы назначаете ref с использованием шаблона обратного вызова ref, в котором ref назначается как ref={node => wrapper.current = node}.

Таким образом каждый раз, когда ваше видимое состояние истинно, вызывается обратный вызов ref, что приводит к вызову функции useCallback, если вы используете ее как

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

...

<div ref={wrapper} />

Однако приведенный выше код useEffect будет работать правильно и детерминированно, если вы добавляете isVisible в качестве зависимости для useEffect вместо wrapper.current

useEffect(() => {
    console.count('effect modal');
    console.log(wrapper.current);
}, [isVisible]) 

, и это тоже правильный способ go, поскольку ваш ref изменяется только при изменении флага isVisible и в зависимости от изменяемого значения, так как useEffect не является отличным решением, поскольку могут возникнуть некоторые случаи, когда мутация не сопровождается повторным рендерингом одновременно, и useEffect не будет работать вообще в этом случае

...