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

React Suspense网络请求出现瀑布流问题排查求助

React Suspense + React Query 网络瀑布流问题排查

我正在学习React Suspense相关内容,参考的示例中不存在网络瀑布流,但我编写如下代码执行时,却持续出现网络延迟,请问是否存在遗漏的配置或代码问题?

问题代码

index.tsx

import { useRef, useState, Suspense } from "react";
import TodoList from "./TodoList";
import Contact from "./Contact";

export default function Home() {
  const [query, setQuery] = useState(1);
  const [count, setCount] = useState(1);

  const selectTab = () => {
    setCount(query);
  };

  return (
    <>
      <input
        type="text"
        onChange={(e) => setQuery(Number(e.target.value))}
        value={query}
      />
      <button type="button" onClick={selectTab}>
        post (slow)
      </button>
      <Suspense fallback={<h1>🌀 Loading...</h1>}>
        <TodoList userId={count} />
      </Suspense>
    </>
  );
}

TodoList.tsx

import {
  memo,
  forwardRef,
  useImperativeHandle,
  useRef,
  useEffect,
  useLayoutEffect,
  useState,
  useReducer,
  Suspense,
} from "react";
import Contact from "./Contact";
import { useQuery } from "react-query";
import axios from "axios";

const TodoList = ({ children, userId }: any, ref: React.Ref<any>) => {
  const [loading, setLoading] = useState(true);

  async function fetchUser(userId: number, delay = 0) {
    const response = await axios.get(
      `https://jsonplaceholder.typicode.com/users/${userId}`
    );
    const user = response.data;
    await new Promise((resolve) => setTimeout(resolve, delay)); // 模拟延迟
    return user;
  }

  const { data: user, isLoading } = useQuery(
    [userId],
    () => fetchUser(userId),
    {
      suspense: true,
      refetchOnWindowFocus: false,
      retry: false,
    }
  );

  if (!user) {
    return <h1>empty user data</h1>;
  }
  return (
    <div>
      <p>{user.name} 님이 작성한 글</p>
      {children}
      <Suspense fallback={<h4>글 목록 가져오는중...</h4>}>
        <Contact userId={userId} />
      </Suspense>
    </div>
  );
};

export default TodoList;

Contact.tsx

import { useQuery } from "react-query";
import axios from "axios";

function Contact({ userId }: any) {
  async function fetchPost(userId: number, delay = 0) {
    const response = await axios.get(
      `https://jsonplaceholder.typicode.com/posts?userId=${userId}`
    );
    const post = await response.data;
    await new Promise((resolve) => setTimeout(resolve, delay)); // 模拟延迟
    return post;
  }

  const { data: posts, isLoading } = useQuery(
    ["post", userId],
    () => fetchPost(userId),
    {
      suspense: true,
      refetchOnWindowFocus: false,
      retry: false,
    }
  );

  if (!posts) {
    return <span>empty posts data</span>;
  }

  return (
    <ul>
      {posts.map((post: any) => (
        <li key={post.id}>
          {post.id}. {post.title}
        </li>
      ))}
    </ul>
  );
}

export default Contact;

请求时序图

网络请求时序图


问题原因

你的代码出现网络瀑布流(串行请求)的核心原因是:组件渲染顺序导致请求串行触发。

  1. 首先TodoList组件挂载,触发useQuery请求用户数据
  2. 只有当用户数据返回、TodoList完成渲染后,Contact组件才会被挂载,进而触发它内部的useQuery请求帖子数据
  3. 两个请求串行执行,总耗时是两个请求时间之和,所以会出现明显的网络延迟

解决方案

方案1:在TodoList中并行发起两个请求

使用React Query的useQueries钩子,在TodoList中同时发起用户和帖子的请求,再将帖子数据传递给Contact,避免Contact单独发起请求。

修改TodoList.tsx:

import { useQueries } from "react-query"; // 新增导入

const TodoList = ({ children, userId }: any, ref: React.Ref<any>) => {
  // 新增fetchPost函数(也可移到公共工具文件)
  async function fetchPost(userId: number, delay = 0) {
    const response = await axios.get(
      `https://jsonplaceholder.typicode.com/posts?userId=${userId}`
    );
    const post = await response.data;
    await new Promise((resolve) => setTimeout(resolve, delay));
    return post;
  }

  // 并行发起两个查询
  const [userQuery, postsQuery] = useQueries({
    queries: [
      {
        queryKey: [userId],
        queryFn: () => fetchUser(userId),
        suspense: true,
        refetchOnWindowFocus: false,
        retry: false,
      },
      {
        queryKey: ["post", userId],
        queryFn: () => fetchPost(userId),
        suspense: true,
        refetchOnWindowFocus: false,
        retry: false,
      },
    ],
  });

  const user = userQuery.data;
  const posts = postsQuery.data;

  if (!user) {
    return <h1>empty user data</h1>;
  }

  return (
    <div>
      <p>{user.name} 님이 작성한 글</p>
      {children}
      <Suspense fallback={<h4>글 목록 가져오는중...</h4>}>
        {/* 直接传递posts数据,无需再传userId */}
        <Contact posts={posts} />
      </Suspense>
    </div>
  );
};

修改Contact.tsx,直接使用传入的帖子数据:

function Contact({ posts }: any) {
  if (!posts) {
    return <span>empty posts data</span>;
  }

  return (
    <ul>
      {posts.map((post: any) => (
        <li key={post.id}>
          {post.id}. {post.title}
        </li>
      ))}
    </ul>
  );
}

方案2:在父组件中预加载请求

利用React Query的prefetchQuery,在点击按钮时提前并行发起两个请求,等请求完成后再更新状态渲染组件。

修改Home.tsx:

import { useRef, useState, Suspense } from "react";
import TodoList from "./TodoList";
import { useQueryClient } from "react-query"; // 新增导入
import axios from "axios";

export default function Home() {
  const queryClient = useQueryClient();
  const [query, setQuery] = useState(1);
  const [count, setCount] = useState(1);

  // 定义请求函数
  async function fetchUser(userId: number) {
    const response = await axios.get(
      `https://jsonplaceholder.typicode.com/users/${userId}`
    );
    await new Promise((resolve) => setTimeout(resolve, 0));
    return response.data;
  }

  async function fetchPost(userId: number) {
    const response = await axios.get(
      `https://jsonplaceholder.typicode.com/posts?userId=${userId}`
    );
    await new Promise((resolve) => setTimeout(resolve, 0));
    return response.data;
  }

  const selectTab = async () => {
    // 并行预加载两个请求
    await Promise.all([
      queryClient.prefetchQuery([query], () => fetchUser(query), {
        suspense: true,
      }),
      queryClient.prefetchQuery(["post", query], () => fetchPost(query), {
        suspense: true,
      }),
    ]);
    // 预加载完成后再更新状态
    setCount(query);
  };

  return (
    <>
      <input
        type="text"
        onChange={(e) => setQuery(Number(e.target.value))}
        value={query}
      />
      <button type="button" onClick={selectTab}>
        post (slow)
      </button>
      <Suspense fallback={<h1>🌀 Loading...</h1>}>
        <TodoList userId={count} />
      </Suspense>
    </>
  );
}

这样点击按钮时会同时发起两个请求,完成后再渲染组件,彻底避免瀑布流问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 18:26:04