React中如何在onClick执行期间禁用按钮并自动恢复?
解决React遗留应用中按钮重复点击的问题
问题分析
当前代码的核心问题是耗时的同步操作阻塞了React渲染线程,导致btnLock状态更新后,Button组件无法及时重新渲染,依然会接收点击事件并排队执行。另外,await setState是错误用法——setState不返回Promise,无法通过await等待状态更新完成。
解决方案
需要做两处关键修改:
- 将耗时操作转为异步执行,释放主线程让React能及时更新UI
- 在Button组件中通过
disabled属性从DOM层面禁用按钮,配合状态判断实现双重拦截
修改后的App组件代码
import "./App.css"; import React, { Component } from "react"; import Button from "./components/button"; class App extends Component { constructor(props) { super(props); this.handleClick = this.handleClick.bind(this); this.state = { counter: 0, btnLock: false, }; } async handleClick() { // 立即锁定按钮 this.setState({ counter: this.state.counter + 1, btnLock: true }); try { // 将耗时操作包装为Promise,放到异步队列执行,不阻塞主线程 await new Promise(resolve => { var i = 0; var text; while (i < 20000000) { text = "The number is " + i; i++; } resolve(); }); console.log("click" + this.state.counter); } finally { // 无论操作成功失败,最终都解锁按钮 this.setState({ btnLock: false }); } }; render() { return <Button handleClick={this.handleClick} btnLock={this.state.btnLock} />; } } export default App;
修改后的Button组件代码
import React, { Component } from "react"; class Button extends Component { constructor(props){ super(props); this.handleClick = this.handleClick.bind(this); } handleClick() { // 状态层面拦截重复点击 if (!this.props.btnLock) { this.props.handleClick(); } }; render() { // DOM层面禁用按钮,用户连点击事件都触发不了 return <button onClick={this.handleClick} disabled={this.props.btnLock} >BUTTON</button>; } } export default Button;
关键说明
- 异步耗时操作:把同步循环放到
Promise中,通过await等待完成,这样不会阻塞主线程,React能及时更新btnLock状态并重新渲染Button组件。 - 双重拦截机制:
- DOM层面的
disabled属性直接让按钮无法被点击,从根源阻止事件触发 - 状态判断作为兜底,即使DOM禁用失效,也不会执行重复的处理函数
- DOM层面的
- finally解锁:用
try/finally确保无论耗时操作成功还是失败,按钮最终都会解锁,避免出现永久禁用的情况。
内容的提问来源于stack exchange,提问作者Gage Shaw
相关产品推荐
相关产品推荐

