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
相关产品推荐
相关产品推荐

