React 18 Fetch预渲染传参引发无限重渲染循环的问题与修复
React 18 Suspense 无限重渲染循环问题分析与修复
问题描述
在使用React 18的Suspense组件时,发现多数示例未给Suspense包裹的子组件传递props。当给Fetch API传入参数后,出现了无限重渲染循环,需要明确错误原因及修复方法。
引发问题的代码
import './App.css'; import {Suspense} from "react" const wrapPromise = (promise, todo_id) => { let status = "pending" let response; const suspender = promise(todo_id).then( (res) => { status = "success" response = res }, (err) => { status = "error" response = err }, ) const read = () => { switch (status) { case "pending": throw suspender case "error": throw response default: return response } } return {read}; } const fetchTodo = (todo_id) => fetch("https://jsonplaceholder.typicode.com/todos/" + todo_id) .then(response => response.json()); const wrappedFetchTodoRerenderLoop = (todo_id) => wrapPromise(fetchTodo, todo_id) const MyTodo = (props) => { console.log("redner") const todo = wrappedFetchTodoRerenderLoop(props.todo_id).read(); return ( <> <p>ID: {todo.id}</p> <p>Title: {todo.title}</p> </> ); } function App() { return ( <div className="App"> <Suspense fallback={<p>Loading ...</p>} > <MyTodo todo_id={1}/> </Suspense> </div> ); } export default App;
错误原因
- 无状态持久化:每次组件渲染时,
wrappedFetchTodoRerenderLoop(props.todo_id)都会创建全新的wrapPromise实例,其内部状态始终初始化为pending - 重复抛出Promise:每次调用
read()都会抛出新的suspender Promise,React会持续重新渲染组件以等待Promise完成,形成无限循环 - 无请求缓存:没有对请求结果做缓存,每次渲染都会发起新的网络请求,进一步触发循环
修复后的代码
import './App.css'; import {Suspense} from "react" let cache = new Map(); // 修复点:必须添加请求缓存 const fetchData = (url) => { if (!cache.has(url)) { cache.set(url, fetch(url).then(r => r.json())); } return cache.get(url); } // 修复点:追踪Promise状态,避免重复抛出 function use(promise) { if (promise.status === "fulfilled") { return promise.value; } else if (promise.status === "rejected") { throw promise.reason; } else if (promise.status === "pending") { throw promise; } else { promise.status = "pending"; promise.then( (result) => { promise.status = "fulfilled"; promise.value = result; }, (reason) => { promise.status = "rejected"; promise.reason = reason; } ); throw promise; } } const MyTodo = (props) => { console.log("redner") const todo = use(fetchData(`https://jsonplaceholder.typicode.com/todos/${props.todo_id}`)) return ( <> <p>ID: {todo.id}</p> <p>Title: {todo.title}</p> </> ); } function App() { return ( <div className="App"> <Suspense fallback={<p>Loading ...</p>} > <MyTodo todo_id={1}/> </Suspense> </div> ); } export default App;
修复说明
- 请求缓存:使用
Map作为缓存容器,相同URL的请求只会发起一次,确保后续渲染能直接读取已完成的Promise结果,避免重复请求和状态重置 - Promise状态追踪:通过给Promise对象添加
status、value、reason属性,实时追踪其状态变化。当Promise完成后,组件渲染时会直接返回结果,不会再抛出pending状态的Promise,终止无限循环
内容的提问来源于stack exchange,提问作者Tomek
相关产品推荐
相关产品推荐

