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

React.js中滚动事件自动修改isloading状态问题求助

问题描述

在实现基于API的懒加载分页功能时,存在输入框搜索触发API和滚动触发API两个逻辑。当输入搜索时,因数据量大请求耗时久,已将isloading设为true,意图阻止滚动触发新请求,但在API请求期间滚动页面,isloading状态会被判定为false,导致滚动仍会触发请求。

复现代码
const [isloading, setIsloading] = useState(false);

const onChange = (e) => {
  clearTimeout(timeout);

  timeout = setTimeout(function () {
    setChecked(false);
    setCounter(0);
    setCards([]);
    let new_payload = payload;
    new_payload.skip = 0;

    if (isloading === false) {
      fetching.postData(e.target.value, new_payload, true);
      setSearchInputValue(e.target.value);
      setIsloading(true);
    }
  }, 1000);
};

window.addEventListener('scroll', () => {
  let screen_height = window.innerHeight;
  let scrollTop = document.documentElement.scrollTop;
  var body = document.body,
    html = document.documentElement;

  var body_heigth = Math.max(
    body.scrollHeight,
    body.offsetHeight,
    html.clientHeight,
    html.scrollHeight,
    html.offsetHeight
  );

  console.log(isloading);
  let is_loading = isloading;
  if (scrollTop + 1 > body_heigth - screen_height && is_loading === false && checked === false) {
    is_loading = true;
    setIsloading(is_loading);
    postData(searchInputValue, payload);
  }
});
解决方案

1. 解决闭包导致的状态获取旧值问题

滚动事件监听在组件初始化时绑定,闭包会捕获当时的isloading初始值(false),后续状态更新后,事件回调里拿到的依然是旧值。使用useRef保存最新状态:

const isloadingRef = useRef(false);
// 同步状态到ref
useEffect(() => {
  isloadingRef.current = isloading;
}, [isloading]);

2. 正确管理滚动事件的生命周期

直接在组件内调用window.addEventListener会导致每次渲染重复绑定,使用useEffect绑定并在卸载时清理:

useEffect(() => {
  const handleScroll = () => {
    // 滚动逻辑
  };
  window.addEventListener('scroll', handleScroll);
  return () => window.removeEventListener('scroll', handleScroll);
}, [checked, searchInputValue, payload]); // 依赖项根据实际逻辑添加

3. API请求完成后重置加载状态

确保postData请求完成(成功/失败)后,将isloading设为false,避免状态一直停留在true:

// 封装API请求逻辑
const fetchData = async (value, payloadData) => {
  setIsloading(true);
  try {
    await fetching.postData(value, payloadData, true);
    setSearchInputValue(value);
  } catch (err) {
    // 处理错误
  } finally {
    setIsloading(false);
  }
};

4. 用useRef管理防抖定时器

避免定时器引用在组件渲染时丢失,导致clearTimeout失效:

const timeoutRef = useRef(null);

const onChange = (e) => {
  clearTimeout(timeoutRef.current);
  timeoutRef.current = setTimeout(async () => {
    setChecked(false);
    setCounter(0);
    setCards([]);
    const new_payload = { ...payload, skip: 0 }; // 避免直接修改原payload

    if (!isloadingRef.current) {
      await fetchData(e.target.value, new_payload);
    }
  }, 1000);
};

完整修改后的代码示例

import { useState, useEffect, useRef } from 'react';

const YourComponent = () => {
  const [isloading, setIsloading] = useState(false);
  const [checked, setChecked] = useState(false);
  const [counter, setCounter] = useState(0);
  const [cards, setCards] = useState([]);
  const [searchInputValue, setSearchInputValue] = useState('');
  const [payload, setPayload] = useState({ skip: 0 });
  const isloadingRef = useRef(false);
  const timeoutRef = useRef(null);

  // 同步isloading状态到ref
  useEffect(() => {
    isloadingRef.current = isloading;
  }, [isloading]);

  // 封装API请求逻辑
  const fetchData = async (value, payloadData) => {
    setIsloading(true);
    try {
      await fetching.postData(value, payloadData, true);
      setSearchInputValue(value);
    } catch (error) {
      console.error('请求失败:', error);
    } finally {
      setIsloading(false);
    }
  };

  const onChange = (e) => {
    clearTimeout(timeoutRef.current);
    timeoutRef.current = setTimeout(async () => {
      setChecked(false);
      setCounter(0);
      setCards([]);
      const new_payload = { ...payload, skip: 0 };

      if (!isloadingRef.current) {
        await fetchData(e.target.value, new_payload);
      }
    }, 1000);
  };

  // 滚动事件处理
  useEffect(() => {
    const handleScroll = () => {
      const screen_height = window.innerHeight;
      const scrollTop = document.documentElement.scrollTop;
      const body = document.body;
      const html = document.documentElement;

      const body_height = Math.max(
        body.scrollHeight,
        body.offsetHeight,
        html.clientHeight,
        html.scrollHeight,
        html.offsetHeight
      );

      if (scrollTop + 1 > body_height - screen_height && !isloadingRef.current && !checked) {
        setIsloading(true);
        // 处理分页payload,示例为每页增加10条
        const new_payload = { ...payload, skip: payload.skip + 10 };
        fetching.postData(searchInputValue, new_payload)
          .then(() => setPayload(new_payload))
          .catch(err => console.error('滚动加载失败:', err))
          .finally(() => setIsloading(false));
      }
    };

    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, [checked, searchInputValue, payload]);

  return (
    <input onChange={onChange} value={searchInputValue} placeholder="输入搜索内容" />
    {/* 渲染列表内容 */}
  );
};

export default YourComponent;

内容的提问来源于stack exchange,提问作者ctamyildirim

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 14:06:18