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

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;

关键修正点说明

  1. 解决localStorage超限:

    • 移除localStorage.clear(),避免清空必要的持久化数据。
    • 用帖子自身id作为组件key,避免组件频繁重挂载。
    • 无限滚动添加加载状态锁,防止重复请求导致数据过度累加。
  2. 修复API请求问题:

    • 移除9秒请求延迟,改为初始化时直接请求。
    • 为所有fetch添加错误捕获,避免请求失败导致应用卡死。
    • 优化过滤逻辑,处理posts或value为空的边界情况。
    • 统一状态管理逻辑,减少手动fetch与RTK Query的冲突。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 02:45:02