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

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;

错误原因

  1. 无状态持久化:每次组件渲染时,wrappedFetchTodoRerenderLoop(props.todo_id)都会创建全新的wrapPromise实例,其内部状态始终初始化为pending
  2. 重复抛出Promise:每次调用read()都会抛出新的suspender Promise,React会持续重新渲染组件以等待Promise完成,形成无限循环
  3. 无请求缓存:没有对请求结果做缓存,每次渲染都会发起新的网络请求,进一步触发循环

修复后的代码

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;

修复说明

  1. 请求缓存:使用Map作为缓存容器,相同URL的请求只会发起一次,确保后续渲染能直接读取已完成的Promise结果,避免重复请求和状态重置
  2. Promise状态追踪:通过给Promise对象添加status、value、reason属性,实时追踪其状态变化。当Promise完成后,组件渲染时会直接返回结果,不会再抛出pending状态的Promise,终止无限循环

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 07:33:21