React+TypeScript中如何避免按钮快速点击重复执行懒加载数据加载?
这是前端开发里特别常见的场景!快速点击按钮触发重复异步请求的问题,在React+TypeScript技术栈里有几种非常符合惯用规范的解决方案,我给你逐个拆解清楚:
解决方案1:状态锁(Loading 标记)
这是最直观、最通用的方案——通过一个状态标记当前是否正在加载,点击时先判断这个状态,避免重复触发请求。
类组件示例:
import React from 'react'; class MyComponent extends React.Component { state = { lazyLoadedData: undefined, isLoading: false, // 新增加载状态标记 }; async loadData() { // 模拟异步请求 const response = await fetch('/api/data'); const data = await response.json(); this.setState({ lazyLoadedData: data }); } async handleClick() { // 若正在加载或数据已存在,直接返回 if (this.state.isLoading || this.state.lazyLoadedData) return; this.setState({ isLoading: true }); try { await this.loadData(); } catch (err) { // 处理请求错误 console.error('数据加载失败:', err); } finally { // 无论成功失败,都重置加载状态 this.setState({ isLoading: false }); } } render() { return ( <button onClick={() => this.handleClick()} disabled={this.state.isLoading}> {this.state.isLoading ? '加载中...' : '加载数据'} </button> ); } }
函数组件示例(更常用):
import { useState } from 'react'; type Data = { /* 定义你的数据类型 */ }; const MyComponent = () => { const [lazyLoadedData, setLazyLoadedData] = useState<Data | undefined>(); const [isLoading, setIsLoading] = useState(false); const loadData = async () => { const res = await fetch('/api/data'); const data = await res.json(); setLazyLoadedData(data); }; const handleClick = async () => { if (isLoading || lazyLoadedData) return; setIsLoading(true); try { await loadData(); } catch (err) { console.error('数据加载失败:', err); } finally { setIsLoading(false); } }; return ( <button onClick={handleClick} disabled={isLoading}> {isLoading ? '加载中...' : '加载数据'} </button> ); };
解决方案2:防抖(Debounce)
如果业务允许延迟执行(比如用户连续点击只触发最后一次请求),可以用防抖函数来限制请求频率。常用lodash.debounce,也可以自己实现。
类组件示例:
import React from 'react'; import debounce from 'lodash/debounce'; class MyComponent extends React.Component { state = { lazyLoadedData: undefined, }; async loadData() { const response = await fetch('/api/data'); const data = await response.json(); this.setState({ lazyLoadedData: data }); } // 封装防抖后的加载函数,300ms内重复点击只执行最后一次 debouncedLoad = debounce(async () => { if (!this.state.lazyLoadedData) { await this.loadData(); } }, 300); handleClick = () => { this.debouncedLoad(); }; // 组件卸载时取消防抖任务,避免内存泄漏 componentWillUnmount() { this.debouncedLoad.cancel(); } render() { return <button onClick={this.handleClick}>加载数据</button>; } }
解决方案3:AbortController 取消重复请求
如果你的loadData是基于fetch的异步请求,可以用AbortController取消之前未完成的请求,避免多个请求返回顺序混乱的问题。
类组件示例:
import React from 'react'; class MyComponent extends React.Component { state = { lazyLoadedData: undefined, }; abortController: AbortController | null = null; async loadData(signal?: AbortSignal) { const response = await fetch('/api/data', { signal }); const data = await response.json(); this.setState({ lazyLoadedData: data }); } async handleClick() { if (this.state.lazyLoadedData) return; // 取消之前未完成的请求 if (this.abortController) { this.abortController.abort(); } this.abortController = new AbortController(); try { await this.loadData(this.abortController.signal); } catch (err) { // 忽略主动取消的错误 if ((err as Error).name !== 'AbortError') { console.error('数据加载失败:', err); } } finally { this.abortController = null; } } componentWillUnmount() { this.abortController?.abort(); } render() { return <button onClick={() => this.handleClick()}>加载数据</button>; } }
总的来说,状态锁是最推荐的通用方案,逻辑简单且用户体验清晰;如果业务允许延迟执行,防抖是不错的选择;如果需要处理请求竞争问题(比如多个请求返回顺序不确定),AbortController会更合适。
内容的提问来源于stack exchange,提问作者aioobe
相关产品推荐
相关产品推荐

