React组件中useState状态更新异常:setShowWebsite无法将状态置为false
问题诊断与解决方案
哦,这个问题我太熟了!你这是碰到了React里经典的事件冒泡坑啊😅
问题根源
你的父组件BookWrapper上绑定了onClick={() => setShowWebsite(true)},而子组件BookWebsite是渲染在BookWrapper内部的。当你点击子组件里的关闭按钮时,点击事件会沿着DOM树向上冒泡,最终触发BookWrapper上的点击事件——这就导致你刚调用setShowWebsite(false)把状态改成false,立刻又被父组件的事件改回true了,看起来就像是状态根本没变化,但实际上两次状态更新都执行了!
你可以检查useEffect里的console.log,点击关闭按钮时应该会先输出false,紧接着又输出true,这就是最好的证明。
解决方案(三种可选)
方案1:在子组件的点击事件中阻止冒泡
最简单的办法就是在关闭按钮的点击事件里阻止事件向上冒泡,这样父组件的点击事件就不会被触发了:
const BookWebsite: React.FC<Props> = ({ name, description, setShowWebsite }) => { const closeWebsite = (e: React.MouseEvent) => { e.stopPropagation(); // 阻止事件冒泡 setShowWebsite(false); console.log('element was clicked'); } return( <BookWindow> <IconWrapper onClick={closeWebsite}> <i className="fa-solid fa-x"></i> </IconWrapper> <h1>{name}</h1> <p>{description}</p> </BookWindow> ) }
方案2:调整父组件的点击触发区域
如果逻辑上只有点击书名/作者区域才应该打开弹窗,那可以把父组件的点击事件从BookWrapper移到BookName和BookAuthor上,这样子组件的点击就不会触发打开逻辑了:
const Book: React.FC<{ name: string; author: string }> = ({ name, author }) => { const [showWebsite, setShowWebsite] = useState(false); useEffect(() => console.log(showWebsite), [showWebsite]); const openWebsite = () => setShowWebsite(true); return ( <BookWrapper> {showWebsite ? <BookWebsite name={name} description={author} setShowWebsite={setShowWebsite} /> : undefined} <BookName onClick={openWebsite}> <h3>{name}</h3> </BookName> <BookAuthor onClick={openWebsite}> <h3>{author}</h3> </BookAuthor> </BookWrapper> ); };
方案3:在父组件中判断点击目标
如果必须保留BookWrapper上的点击事件,也可以通过判断点击目标是否属于子组件区域来避免冲突(相对繁琐,需配合ref使用):
const Book: React.FC<{ name: string; author: string }> = ({ name, author }) => { const [showWebsite, setShowWebsite] = useState(false); const bookWindowRef = useRef<HTMLDivElement>(null); useEffect(() => console.log(showWebsite), [showWebsite]); const openWebsite = (e: React.MouseEvent) => { // 如果点击的是子组件区域,就不执行打开逻辑 if (bookWindowRef.current && bookWindowRef.current.contains(e.target as Node)) { return; } setShowWebsite(true); }; // 注意:子组件需要用forwardRef接收ref,此处省略子组件的ref适配代码 return ( <BookWrapper onClick={openWebsite}> {showWebsite ? <BookWebsite ref={bookWindowRef} name={name} description={author} setShowWebsite={setShowWebsite} /> : undefined} <BookName> <h3>{name}</h3> </BookName> <BookAuthor> <h3>{author}</h3> </BookAuthor> </BookWrapper> ); };
验证方法
修改后你可以再观察useEffect里的console.log,点击关闭按钮时应该只会输出false,状态就能正常保持了。
内容的提问来源于stack exchange,提问作者Gurajj
相关产品推荐
相关产品推荐

