Chrome控制台Storage配额超限DOMException,API请求异常求助
问题排查与代码修正
错误原因分析
1. localStorage配额超限(persist:root超出存储限制)
- 原代码中
localStorage.clear()仅能临时清空存储,无法解决根源问题:若项目用redux-persist持久化状态,无限滚动累加的大量帖子数据会超出localStorage的5MB配额,触发setItem失败。 - 用
nanoid()作为组件key会导致每次渲染生成新key,组件频繁重挂载,加剧不必要的状态更新与存储压力。
2. 修改代码后API请求失败
- 第一个文件中初始请求设置9秒延迟,导致应用初始化阶段无法正常获取数据;且未处理请求失败场景,容易引发状态异常。
localStorage.clear()会清除RTK Query缓存或redux-persist的持久化状态,导致应用状态紊乱,需清除浏览器缓存才能恢复。- 手动fetch与RTK Query同时存在,可能引发请求冲突或状态逻辑混乱。
修正后的代码
第一个文件(PostsList)
import { nanoid } from "@reduxjs/toolkit"; import tw from 'twin.macro'; import Advertise from "../../pages/PostPage/components/Advertise"; import Placeholder from "../Placeholder"; import Post from "./components/Post"; import { useEffect, useState } from "react"; import LoadingSpinner from "../LoadingSpinner/LoadingSpinner"; const PostsList = ({ filteredTag, toInvalidate, enableImages = true }) => { const advertise = async () => { window?.EventBus?.emit("reset-ads"); }; useEffect(() => { advertise(); }, []); const [posts, setPosts] = useState([]); const [loading, setLoading] = useState(true); const [isFetchingMore, setIsFetchingMore] = useState(false); // 初始获取帖子 useEffect(() => { const fetchInitialPosts = async () => { try { const res = await fetch(`${process.env.BASE_URL}/posts?limit=8`); const data = await res.json(); setPosts(data); } catch (err) { console.error("初始获取帖子失败:", err); } finally { setLoading(false); } }; fetchInitialPosts(); }, []); // 无限滚动逻辑 useEffect(() => { const fetchAgain = async () => { if (isFetchingMore || posts.length === 0) return; setIsFetchingMore(true); try { const skip = posts.length; const res = await fetch(`${process.env.BASE_URL}/posts?limit=4&skip=${skip}`); const result = await res.json(); // 仅当有新数据时更新列表 if (result.length > 0) { setPosts(prev => [...prev, ...result]); } } catch (err) { console.error("加载更多帖子失败:", err); } finally { setIsFetchingMore(false); } }; const handleScroll = () => { const html = document.documentElement; const windowHeight = window.innerHeight || html.offsetHeight; const docHeight = Math.max( document.body.scrollHeight, document.body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight ); const windowBottom = windowHeight + window.pageYOffset; // 接近底部时提前触发加载 if (windowBottom >= docHeight - 100 && !isFetchingMore) { fetchAgain(); } }; window.addEventListener("scroll", handleScroll); return () => window.removeEventListener("scroll", handleScroll); }, [posts, isFetchingMore]); if (loading) { return <LoadingSpinner />; } return ( <Wrapper> {posts.length > 0 ? ( posts.map((post, i) => ( <div key={post.id}> <Post post={post} isFirstPost={enableImages && i === 0} filteredTag={filteredTag} toInvalidate={toInvalidate} /> <Advertise /> </div> )) ) : ( <Placeholder /> )} {isFetchingMore && <LoadingSpinner />} </Wrapper> ); }; const Wrapper = tw.div`w-full`; export default PostsList;
第二个文件(Posts)
import { useEffect, useState } from 'react'; import tw from 'twin.macro'; import LoadingSpinner from '../../../common/LoadingSpinner'; import { useGetPostsQuery } from '../../../core/features/posts/postsApiSlice'; import Post from '../../../common/PostsList/components/Post'; import Placeholder from '../../../common/Placeholder/Placeholder'; const PostsList = ({ posts, filteredTag, toInvalidate, enableImages = true }) => { return ( <Wrapper> {posts?.length > 0 ? ( posts.map((post) => ( <Post key={post.id} post={post} isFirstPost={enableImages && false} filteredTag={filteredTag} toInvalidate={toInvalidate} /> )) ) : ( <Placeholder /> )} </Wrapper> ); }; const Posts = ({ value }) => { const { data: posts, isLoading, error } = useGetPostsQuery(null, { refetchOnMountOrArgChange: true, }); const [filteredPosts, setFilteredPosts] = useState([]); useEffect(() => { if (!posts) return; // 处理空值边界情况 const filtered = posts.filter(post => post.title?.toLowerCase().includes(value?.toLowerCase() || '') ); setFilteredPosts(filtered); }, [value, posts]); if (isLoading) { return <LoadingSpinner />; } if (error) { return <Placeholder />; } return <PostsList posts={filteredPosts} enableImages={false} />; }; const Wrapper = tw.div``; export default Posts;
关键修正点说明
解决localStorage超限:
- 移除
localStorage.clear(),避免清空必要的持久化数据。 - 用帖子自身
id作为组件key,避免组件频繁重挂载。 - 无限滚动添加加载状态锁,防止重复请求导致数据过度累加。
- 移除
修复API请求问题:
- 移除9秒请求延迟,改为初始化时直接请求。
- 为所有fetch添加错误捕获,避免请求失败导致应用卡死。
- 优化过滤逻辑,处理
posts或value为空的边界情况。 - 统一状态管理逻辑,减少手动fetch与RTK Query的冲突。
内容的提问来源于stack exchange,提问作者samurai
相关产品推荐
相关产品推荐

