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

React副作用循环疑问:为何示例与自制代码表现不同?

React渲染机制:模拟代码为何未出现无限循环?

问题背景

我是React新手,为理解React渲染机制,阅读了一篇关于memoization的文章,遇到如下示例代码:

function App() {
  const [body, setBody] = React.useState()
  const [status, setStatus] = React.useState('idle')

  const fetchConfig = {
    method: 'POST',
    body,
    headers: {'content-type': 'application/json'},
  }
  const makeFetchRequest = () => (body ? fetch('/post', fetchConfig) : null)

  React.useEffect(() => {
    const promise = makeFetchRequest()

    // if no promise was returned, then we didn't make a request
    // so we'll exit early
    if (!promise) return

    setStatus('pending')

    promise.then(
      () => setStatus('fulfilled'),
      () => setStatus('rejected'),
    )
  }, [makeFetchRequest])

  function handleSubmit(event) {
    event.preventDefault()
    // get form input values
    setBody(formInputValues)
  }

  return (
    <form onSubmit={handleSubmit}>
      {/* form inputs and other neat stuff... */}
    </form>
  )
}

作者指出该App组件会引发无限渲染:
你猜会发生什么?如果答案是“失控的副作用循环”,那你答对了!原因是React.useEffect会在依赖数组中的元素变化时触发回调。我们唯一的依赖是makeFetchRequest,而它在组件内创建,意味着每次渲染都是新的实例。

我尝试用更简单的代码模拟该行为:

import { useEffect, useState } from 'react';

const App = () => {
  const [state, setState] = useState(0);

  const fn = () => {
    setState(1);
  };

  useEffect(() => {
    console.log('side effect');
    fn();
  }, [fn]);
};

export default App;

我原本预期会出现无限渲染:组件重新渲染后,调用fn改变state引发再次渲染,生成新的fn实例,因useEffect依赖fn会再次触发副作用,循环往复。但实际useEffect仅执行两次,与作者示例的无限循环表现不同,我不清楚自己的理解误区在哪。

误区解析

作者示例的无限循环逻辑

当useEffect执行时,会调用makeFetchRequest发起异步请求,同时将status设为pending——这会触发组件重新渲染,生成新的makeFetchRequest实例。等异步请求完成后,又会调用setStatus将状态改为fulfilled或rejected,再次触发渲染并生成新的makeFetchRequest,而useEffect的依赖是这个函数,每次依赖变化都会重新执行副作用,如此循环往复,导致无限渲染。

我的模拟代码仅执行两次的原因

你的模拟代码中,fn每次渲染都会生成新实例,所以第二次渲染时useEffect会因为依赖变化再次执行,但关键在于:

  • 第一次useEffect执行时,调用fn将state从0改为1,触发第二次渲染;
  • 第二次useEffect执行时,再次调用fn,但此时setState(1)的目标值和当前state值(1)完全相同,React会跳过这次状态更新,不会触发第三次渲染;
  • 没有新的渲染,就不会生成新的fn实例,useEffect也就不会再次执行,循环到此终止。

你之前认为“React识别到fn的实现未改变因此不再渲染”是错误的——React并不会比较函数的实现内容,只要函数是新创建的(每次渲染都会重新创建),依赖数组就会判定为变化。真正阻止循环的是状态值没有发生实际变化,React跳过了后续的渲染触发。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 18:55:21