React代码中removeEventListener为何失效?求原因解析
React中removeEventListener无效的原因及解决方案
问题背景
我查阅了多篇Stack Overflow帖子,了解到bind(this)会为函数创建新引用,但仍无法让React代码中的removeEventListener生效。尝试e.target.onClick = null这类方法也不起作用,想明确这是否和React合成事件有关,以及为何removeEventListener不生效。
示例代码
class TodoApp extends React.Component { constructor(props) { super(props); this.arr = [1]; this.handeler = this.handeler.bind(this); // 存储bind后的新引用 } handeler(e) { console.log("click"); e.target.removeEventListener("click", this.handeler); // 理论上应该是同一个引用? e.target.removeEventListener("onclick", this.handeler); e.target.removeEventListener("onClick", this.handeler); } render() { return ( <div> <h2>Tesing react inline bind</h2> { [1].map( () => { return ( <div> <button onClick={this.handeler} >clickMe</button> </div> ) } ) } </div> ) } } ReactDOM.render(<TodoApp />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script> <div id="app"></div>
核心疑问
- 明明存储了
bind(this)后的函数引用,为何removeEventListener无法移除事件监听? - 是否和React的合成事件机制有关?
- 为什么
e.target.onClick = null也不起作用?
原因解析
1. React合成事件的委托机制
React并没有直接把你传入的this.handeler绑定到button元素上,而是采用了事件委托:所有组件事件都会被统一委托到根节点(如document或挂载容器)处理。你通过onClick传入的函数,只是React合成事件系统中的一个回调,从来没有作为原生事件处理函数绑定到DOM元素上。因此调用e.target.removeEventListener去移除this.handeler,自然找不到对应的监听。
2. e.target.onClick = null无效的原因
React的onClick属性和原生DOM的onClick属性完全是两回事——React并没有给button的原生onClick赋值为你的处理函数,所以设置它为null不会影响React的事件监听逻辑。
可行解决方案
方案1:用组件状态控制(React推荐方式)
通过状态变量控制事件监听是否生效,这符合React"UI跟随状态变化"的设计理念:
class TodoApp extends React.Component { constructor(props) { super(props); this.state = { canClick: true }; this.handeler = this.handeler.bind(this); } handeler(e) { if (!this.state.canClick) return; console.log("click"); this.setState({ canClick: false }); } render() { return ( <div> <h2>Testing react inline bind</h2> <div> <button onClick={this.state.canClick ? this.handeler : null} >clickMe</button> </div> </div> ) } }
方案2:手动绑定/移除原生事件(不推荐)
如果一定要用原生事件API,需要在组件挂载时手动给DOM元素绑定事件,卸载时移除:
class TodoApp extends React.Component { constructor(props) { super(props); this.handeler = this.handeler.bind(this); this.buttonRef = React.createRef(); } componentDidMount() { this.buttonRef.current.addEventListener('click', this.handeler); } componentWillUnmount() { this.buttonRef.current.removeEventListener('click', this.handeler); } handeler(e) { console.log("click"); e.target.removeEventListener("click", this.handeler); } render() { return ( <div> <h2>Testing react inline bind</h2> <div> <button ref={this.buttonRef} >clickMe</button> </div> </div> ) } }
内容的提问来源于stack exchange,提问作者user31782
相关产品推荐
相关产品推荐

