React类组件调用setState切换isCartActive状态不更新无报错

问题原因
核心问题是事件捕获阶段触发的全局点击监听和按钮自身点击事件产生了状态更新冲突,叠加不规范的ref写法,导致状态切换失效:
- 你在
componentDidMount中给document绑定了捕获阶段触发的click监听(第三个参数传了true),点击事件会先触发全局的handleClickOutside逻辑,再触发购物车按钮自身的onClick逻辑。 - 当浮层处于打开状态(
isCartActive: true)时,点击购物车按钮:- 首先触发捕获阶段的全局监听:此时浮层已渲染,判断点击目标不在浮层DOM范围内,入队第一个
setState,把isCartActive设为false - 接着触发按钮自身的
handleCartActive:入队第二个函数式setState,对prevState的isCartActive取反 - React按顺序执行更新队列:第一个更新把状态改为
false,第二个函数式更新拿到更新后的false再取反变回true,最终状态还是true,浮层始终无法关闭,看起来就像状态完全没变化。
- 首先触发捕获阶段的全局监听:此时浮层已渲染,判断点击目标不在浮层DOM范围内,入队第一个
- 额外的不规范问题:你把
ref存在了state中,ref是可变的实例对象,不需要放在state里做不可变更新,这种写法会触发不必要的重渲染,存在性能隐患。
修复方案
按以下步骤修改代码即可:
- 把ref从state中移出,直接挂载到组件实例上,同时给购物车触发按钮也加一个ref,用来区分点击来源
构造函数修改为:constructor(){ super(); this.state={ isCartActive:false, } // 浮层ref、触发按钮ref直接挂实例,不放state this.cartOverlayRef = React.createRef(null); this.cartTriggerRef = React.createRef(null); } - 修改全局点击监听逻辑,排除点击触发按钮的场景,避免重复更新状态
handleClickOutside = (event) => { // 点击的是触发按钮本身,直接返回,交给按钮自己的逻辑处理 if (this.cartTriggerRef.current && this.cartTriggerRef.current.contains(event.target)) { return; } if (this.cartOverlayRef.current && !this.cartOverlayRef.current.contains(event.target)) { this.setState({ isCartActive: false }); } } - 把
handleCartActive改成箭头函数形式,和其他事件处理函数保持一致,避免this指向问题,同时简化onClick写法// 改成箭头函数,绑定this到组件实例 handleCartActive = () => { console.log("cart active",this.state.isCartActive); this.setState(prevState =>({ isCartActive:!prevState.isCartActive })) } - 替换JSX中所有旧的ref引用,绑定新的ref,简化onClick绑定
把原来的:
改为:<NavbarItem onClick={()=>{this.handleCartActive()}}>
把原来的:<NavbarItem ref={this.cartTriggerRef} onClick={this.handleCartActive}>
改为:<OverlayContainerWrapper ref={this.state.ref}><OverlayContainerWrapper ref={this.cartOverlayRef}>
修改完成后点击按钮切换浮层、点击外部/按ESC关闭浮层的逻辑都会正常工作。
内容的提问来源于stack exchange,提问作者sarangkkl
相关产品推荐
相关产品推荐

