![](/img/trans.png)
[英]React useCallback hook, not receiving updated dependency state value
[英]When accessing a state variable from a useCallback, value is not updated
在我的代码的某个位置,我正在从回调( UserCallback
)访问我的组件的 state 变量,我发现 state 变量尚未从初始值更新,回调指的是初始值。 正如我在文档中读到的,当变量作为数组项之一传递时,它应该在更新时更新 function。 以下是示例代码。
const Child = forwardRef((props, ref) => {
const [count, setCount] = useState(0);
const node = useRef(null);
useImperativeHandle(ref, () => ({
increment() {
setCount(count + 1);
}
}));
const clickListener = useCallback(
e => {
if (!node.current.contains(e.target)) {
alert(count);
}
},
[count]
);
useEffect(() => {
// Attach the listeners on component mount.
document.addEventListener("click", clickListener);
// Detach the listeners on component unmount.
return () => {
document.removeEventListener("click", clickListener);
};
}, []);
return (
<div
ref={node}
style={{ width: "500px", height: "100px", backgroundColor: "yellow" }}
>
<h1>Hi {count}</h1>
</div>
);
});
const Parent = () => {
const childRef = useRef();
return (
<div>
<Child ref={childRef} />
<button onClick={() => childRef.current.increment()}>Click</button>
</div>
);
};
export default function App() {
return (
<div className="App">
<Parent />
</div>
);
}
我最初构建的是自定义确认模式。 我有一个 state 变量,它将display:block或display:none设置为根元素。 然后,如果在组件外部单击,我需要通过将 state 变量设置为false来关闭模式。 以下是原 function。
const clickListener = useCallback(
(e: MouseEvent) => {
console.log('isVisible - ', isVisible, ' count - ', count, ' !node.current.contains(e.target) - ', !node.current.contains(e.target))
if (isVisible && !node.current.contains(e.target)) {
setIsVisible(false)
}
},
[node.current, isVisible],
)
它不会关闭,因为isVisible始终为false ,这是初始值。
我在这里做错了什么?
为了进一步澄清,以下是完整的组件。
const ConfirmActionModal = (props, ref) => {
const [isVisible, setIsVisible] = useState(false)
const [count, setCount] = useState(0)
const showModal = () => {
setIsVisible(true)
setCount(1)
}
useImperativeHandle(ref, () => {
return {
showModal: showModal
}
});
const node = useRef(null)
const stateRef = useRef(isVisible);
const escapeListener = useCallback((e: KeyboardEvent) => {
if (e.key === 'Escape') {
setIsVisible(false)
}
}, [])
useEffect(() => {
stateRef.current = isVisible;
}, [isVisible]);
useEffect(() => {
const clickListener = e => {
if (stateRef.current && !node.current.contains(e.target)) {
setIsVisible(false)
}
};
// Attach the listeners on component mount.
document.addEventListener('click', clickListener)
document.addEventListener('keyup', escapeListener)
// Detach the listeners on component unmount.
return () => {
document.removeEventListener('click', clickListener)
document.removeEventListener('keyup', escapeListener)
}
}, [])
return (
<div ref={node}>
<ConfirmPanel style={{ display : isVisible ? 'block': 'none'}}>
<ConfirmMessage>
Complete - {isVisible.toString()} - {count}
</ConfirmMessage>
<PrimaryButton
type="submit"
style={{
backgroundColor: "#00aa10",
color: "white",
marginRight: "10px",
margin: "auto"
}}
onClick={() => {console.log(isVisible); setCount(2)}}
>Confirm</PrimaryButton>
</ConfirmPanel>
</div>
)
}
export default forwardRef(ConfirmActionModal)
您在组件 mount上将 function clickListener
分配给document.addEventListener
,这个 function 对count
数值有一个关闭。
在下一次渲染时, count
数值将是陈旧的。
解决它的一种方法是使用参考闭包来实现 function:
const Child = forwardRef((props, ref) => {
const [count, setCount] = useState(0);
const countRef = useRef(count);
useEffect(() => {
countRef.current = count;
}, [count]);
useEffect(() => {
// countRef.current always holds the most updated state
const clickListener = e => {
if (!node.current.contains(e.target)) {
alert(countRef.current);
}
};
document.addEventListener("click", clickListener);
return () => {
document.removeEventListener("click", clickListener);
};
}, []);
...
}
虽然当count
更改时您的clickListener
确实会更改,但您只在安装时绑定初始clickListener
一次,因为您的useEffect
依赖项列表为空。 您也可以将clickListener
添加到依赖项列表中:
useEffect(() => {
// Attach the listeners on component mount.
document.addEventListener("click", clickListener);
// Detach the listeners on component unmount.
return () => {
document.removeEventListener("click", clickListener);
};
}, [clickListener]);
旁注:在依赖列表中使用node.current
不会做任何事情,因为 react 不会注意到对 ref 的任何更改。 依赖项只能是 state 或 props。
您可以将回调传递给 setIsvisible,因此您不需要isVisible
作为useCallback
的依赖项。 添加node.current
是没有意义的,因为 node 是一个 ref 并且会发生变异:
const clickListener = useCallback((e) => {
setIsVisible((isVisible) => {//pass callback to state setter
if (isVisible && !node.current.contains(e.target)) {
return false;
}
return isVisible;
});
}, []);//no dependencies needed
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.