You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 09:10:32