Состояние реакции сбрасывается при установке из обработчика события проп - PullRequest
1 голос
/ 19 сентября 2019

За всю жизнь не могу понять, что происходит, но по какой-то причине, когда нажата кнопка «Нажми меня», число увеличивается, как и следовало ожидать.Когда щелчок запускается дочерним компонентом, он сбрасывает состояние и ВСЕГДА печатает 0.

function Child(props: {
    onClick?: (id: string) => void,
}) {
    const ref = useCallback((ref) => {
        ref.innerHTML = 'This Doesnt';
        ref.addEventListener('click',() => {
            props.onClick!('')
        })
    }, [])
    return (<div ref={ref}></div>)
}

function Parent() {
  const [number, setNumber] = useState(0);
  return <div>
            <div onClick={() => {
                setNumber(number + 1);
                console.log(number);
            }}>
                This Works
            </div>
            <Child
                onClick={(id) => {
                    setNumber(number + 1);
                    console.log(number);;
                }}
            />
        </div>
}

И вот демонстрация проблемы: https://jscomplete.com/playground/s333177

Ответы [ 2 ]

2 голосов
/ 19 сентября 2019

Оба обработчика onClick в родительском компоненте воссоздаются при каждом рендеринге, так как они имеют замыкание в поле состояния number.

Проблема в том, что свойство onClick, отправленное дочернему компоненту, используется в обратном вызове ref, который реагирует только во время первоначального рендеринга из-за пустого списка зависимостей.Поэтому onClick prop, полученный Child в последующих рендерах, вообще не используется.

Попытка устранить эту ошибку, удалив параметр зависимости или отправив props.onClick, как в списке зависимостей, мы попадаем в проблему из-запредостережение, упомянутое в документации.https://reactjs.org/docs/refs-and-the-dom.html

Таким образом, вы добавляете нулевую обработку и видите, что ваш обновленный обратный вызов теперь вызывается, но ... все более ранние обратные вызовы также вызываются, поскольку мы не удалили эти прослушиватели событий.

const ref = useCallback((ref) => {
    if(!ref) return;
    ref.innerHTML = 'This Doesnt';
    ref.addEventListener('click',() => {
        props.onClick!('')
    })
}, [props.onClick])

Я полагаю, что это всего лишь эксперимент, проводимый в рамках изучения хуков, в противном случае нет необходимости идти окольным путем, чтобы вызвать onClick из обратного вызова ref.Просто передайте его как реквизит div.

Редактировать:
Согласно вашему комментарию, поскольку это не просто эксперимент, а упрощение некоторых подлинных требований, где требуется обработчик кликовдля установки через addEventListener, вот возможное решение:

const ref = useRef(null);
useEffect(() => {
    if(!ref.current) return;
    ref.current.innerHTML = 'This Doesnt';
    const onClick = () => props.onClick!('');
    ref.current.addEventListener('click',onClick)

    // return the cleanup function to remove the click handler, which will be called before this effect is run next time.
    return () => {ref.current.removeEventListener("click", onClick)}
}, [ref.current, props.onClick]);

В основном нам нужно использовать useEffect, чтобы у нас была возможность удалить старого слушателя перед добавлением нового.

Надеюсь, это поможет.

1 голос
/ 19 сентября 2019
function Child(props: {
    onClick?: (id: string) => void,
}) {
    function handleClick() {
      props.onClick('')
    }
    const ref = useRef();
    useEffect(() => {
      ref.current.innerHTML = 'This Doesnt';
      ref.current.addEventListener('click', handleClick)
      return () => { ref.current.removeEventListener('click', handleClick); }
    }, [props.onClick])

    return (<div ref={ref}></div>)
}

@ ckder почти работает, но console.log отображает все числа от 0 до текущего значения числа.Проблема была связана с прослушивателем событий, который не был удален после размонтирования дочернего компонента, поэтому для достижения этого я использовал функцию useEffect и return, где я размонтировал прослушиватель.

...