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;
请求时序图

问题原因
你的代码出现网络瀑布流(串行请求)的核心原因是:组件渲染顺序导致请求串行触发。
- 首先
TodoList组件挂载,触发useQuery请求用户数据 - 只有当用户数据返回、
TodoList完成渲染后,Contact组件才会被挂载,进而触发它内部的useQuery请求帖子数据 - 两个请求串行执行,总耗时是两个请求时间之和,所以会出现明显的网络延迟
解决方案
方案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
相关产品推荐
相关产品推荐

