从prop事件处理程序设置时,反应状态会重置

时间:2019-09-18 22:44:24

标签: javascript reactjs react-hooks

我一生无法知道发生了什么,但是由于某些原因,当单击“单击我”时,数字会按预期增加。当子组件触发点击时,它将重置状态并始终打印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 个答案:

答案 0 :(得分:2)

正确地在每个渲染器上重新创建父组件中的onClick处理程序,因为它们在number状态字段上具有关闭符。

问题是,发送到Child组件的onClick属性在ref回调中使用,由于依赖列表为空,因此仅在初始渲染期间做出了反应。因此,Child在后续渲染中收到的onClick道具根本无法使用。

尝试通过删除依赖项参数或发送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])

我认为,这只是作为学习挂钩的一部分而进行的实验,否则,无需采取round回的方式从ref回调中调用onClick。只需将它作为道具传递给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 :(得分:1)

ip=192.168.1.100::192.168.1.1:255.255.255.0::eth0

@ckder几乎可以使用,但是console.log显示了从0到当前数字值的所有数字。  事件侦听器的问题在于卸载子组件之后尚未删除事件侦听器,因此要达到此目的,我在卸载侦听器的地方使用了useEffect和return函数。