Javascript 当从prop事件处理程序设置时,反应状态重置
我一辈子都搞不清楚到底发生了什么,但由于某种原因,当点击“Click me”时,数字会像你所期望的那样增加。当子组件触发单击时,它将重置状态并始终打印0Javascript 当从prop事件处理程序设置时,反应状态重置,javascript,reactjs,react-hooks,Javascript,Reactjs,React Hooks,我一辈子都搞不清楚到底发生了什么,但由于某种原因,当点击“Click me”时,数字会像你所期望的那样增加。当子组件触发单击时,它将重置状态并始终打印0 function Child(props: { onClick?: (id: string) => void, }) { const ref = useCallback((ref) => { ref.innerHTML = 'This Doesnt'; ref.addEventList
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>
}
函数子级(道具:{
onClick?:(id:string)=>void,
}) {
常量ref=useCallback((ref)=>{
ref.innerHTML='This Doesnt';
ref.addEventListener('单击',()=>{
道具。onClick!(“”)
})
}, [])
返回()
}
函数父函数(){
const[number,setNumber]=useState(0);
返回
{
设置编号(编号+1);
控制台日志(编号);
}}>
这很有效
{
设置编号(编号+1);
控制台日志(编号);;
}}
/>
}
下面是问题的演示:父组件中的
onClick
处理程序在每次渲染时都会重新创建,因为它们在number
状态字段上有一个闭包
问题是,发送到子组件的onClick
属性在ref
回调中使用,由于依赖项列表为空,只有在初始渲染时才会对其作出反应。因此,子级在后续渲染中接收的onClick
prop根本不会被使用
若要解决此错误,请删除dependency param或发送props.onClick,就像在dependency list中一样,由于文档中提到的警告,我们遇到了问题
因此,您添加了null处理,现在可以调用更新的回调,但是。。。所有早期的回调都会被调用,因为我们没有删除这些事件侦听器
const ref = useCallback((ref) => {
if(!ref) return;
ref.innerHTML = 'This Doesnt';
ref.addEventListener('click',() => {
props.onClick!('')
})
}, [props.onClick])
我相信这只是作为学习挂钩的一部分进行的一个实验,否则就不需要迂回地从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]);
基本上,我们需要使用useffect
,以便在添加新的侦听器之前有机会删除旧的侦听器
希望这有帮助。函数子(道具:{
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>)
}
onClick?:(id:string)=>void,
}) {
函数handleClick(){
道具。onClick(“”)
}
const ref=useRef();
useffect(()=>{
ref.current.innerHTML='This Doesnt';
ref.current.addEventListener('click',handleClick)
return()=>{ref.current.removeEventListener('click',handleClick);}
},[props.onClick])
返回()
}
几乎可以工作,但console.log显示了从0到当前数值的所有数字。
问题是在子组件umount之后未删除的事件侦听器,因此为了实现这一点,我在卸载侦听器时使用了useffect和return函数。在SVGView中使用useCallback()的原因是什么?我想知道这是否是你的问题的原因?获取div的引用。在这种情况下,可能是更好的fitUse ref也有同样的问题..是bang on
props.onClick!(“”)
打字脚本的特殊功能?另外,为什么要将onClick
包装在闭包中,而不是直接将其作为回调参数传递给addEventListener
?在我的简化过程中,所有这些的推理都丢失了。我有一个SVG视图类,它对SVG进行大量操作,因此使用snapsvg。我将点击事件添加到各种svg元素中,并一直试图避免每次道具更改时解析和重新渲染整个svg,但看起来如果我要使用挂钩,我的最佳选择是在每次渲染后重新创建onclick处理程序。在这种情况下,可能会恢复使用类。我已经用可能的解决方案更新了答案。请检查一下。刚刚注意到@kwdowik提供了类似的解决方案。谢谢。这就是我最终要做的。实际上,这一切都比仅仅使用一个类更混乱,但至少它是有趣的!再次感谢。