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

React中DisplayName组件useEffect无依赖持续更新state导致重复渲染问题求助

React中DisplayName组件useEffect无依赖持续更新state导致重复渲染问题求助

问题描述

In the DisplayName component the state keeps updating even though there is no change in state. console.log("Rendered") and console.log(value) keeps printing. I can't understand, can someone help me on this.

Below is the code:

import React, { useRef, useState, useEffect, useMemo } from "react";
import './App.css';
import FComponent from "./FComponent";

function App() {
  const [name, setName] = useState("am");
  const [counter, setCounter] = useState(1);

  const result = useMemo(() => {
    return factorial(counter)
  }, [counter])

  console.log("result")

  const displayName = () => {
    return {name};
  }

  return (
    <div className="App">
      <h1>Factorial of {counter} is: {result}
      </h1>
      <div>
        <button onClick={() => setCounter(counter - 1)}>Decrement</button>
        <button onClick={() => setCounter(counter + 1)}>Increment</button>
      </div>
      <hr></hr>
      <div>
        <div>
          <label>Enter Name</label>
        </div>
        <input
          type="text"
          value={name}
          onChange={(e) => setName(e.target.value)}
        >
        </input>
        <hr></hr>
        <div>
          <DisplayName displayName={displayName} />
        </div>
      </div>
    </div>
  );
}

const DisplayName = ({ displayName }) => {
  console.log("Rendered")

  const [value, setValue] = useState("");
  console.log(value);

  useEffect(() => {
    setValue(displayName());
  })
  return (<p>{`My name is ${value.name}`}</p>)
}

function factorial(n) {
  if (n < 0) {
    return -1
  }
  if (n === 0) {
    return 1
  }
  return n * factorial(n - 1)
}

function ten() {
  return new Date().toString()
}

export default App;

I expected that in DisplayName component, the state named value should only update once via the useEffect from "" to {"am"}. Now once it's updated to {"am"} it should stop updating.


问题分析与解决方案

Hey there! Let's break down why this infinite re-render loop is happening and fix it step by step:

1. 导致循环渲染的核心原因

这里有两个关键问题共同引发了DisplayName组件的持续渲染:

  • 问题1:displayName函数引用随父组件渲染不断变化
    你在App组件内部定义了displayName函数,每次App重新渲染(比如点击加减按钮修改counter时),都会创建一个全新的displayName函数实例。由于这个函数作为props传给DisplayName,React会认为props发生了变化,从而触发DisplayName的重新渲染。

  • 问题2:useEffect无依赖数组 + 对象引用频繁更新
    DisplayName中的useEffect没有设置依赖数组,这意味着每次组件渲染时它都会执行。而内部调用setValue(displayName())时,每次displayName()都会返回一个新的{name}对象——哪怕name的实际值没有变化。React对对象类型的state是按引用比较的,所以会认为state发生了改变,再次触发渲染,形成无限循环。

2. 具体修复方案

我们可以分两种方式来解决,其中第二种更简洁:

方案一:稳定函数引用 + 优化useEffect依赖

  • 用useCallback包裹displayName函数,确保只有当name变化时才重新创建函数引用:
// 在App组件中导入useCallback
import React, { ..., useCallback } from "react";

// 替换原有的displayName定义
const displayName = useCallback(() => {
  return { name };
}, [name]); // 仅当name变化时才重新生成函数
  • 给DisplayName的useEffect添加依赖数组,并在更新state前判断值是否真的变化:
const DisplayName = ({ displayName }) => {
  console.log("Rendered");

  const [value, setValue] = useState({ name: "" });
  console.log(value);

  useEffect(() => {
    const newNameObj = displayName();
    // 仅当name实际值变化时才更新state
    if (newNameObj.name !== value.name) {
      setValue(newNameObj);
    }
  }, [displayName, value]); // 仅当displayName或value变化时执行

  return <p>{`My name is ${value.name}`}</p>;
};

方案二:直接传递name作为props(更简洁)

其实你完全不需要displayName这个中间函数,直接把name作为props传给DisplayName即可,彻底规避函数引用变化的问题:

  • 改造App组件的返回部分:
// 替换原有的<DisplayName displayName={displayName} />
<DisplayName name={name} />
  • 简化DisplayName组件(甚至可以不用本地state):
const DisplayName = ({ name }) => {
  console.log("Rendered");
  // 直接使用props的name,无需本地state
  return <p>{`My name is ${name}`}</p>;
};

如果确实需要本地state,再添加useEffect监听name变化:

const DisplayName = ({ name }) => {
  console.log("Rendered");
  const [value, setValue] = useState(name);

  useEffect(() => {
    setValue(name);
  }, [name]); // 仅当name变化时更新本地state

  console.log(value);
  return <p>{`My name is ${value}`}</p>;
};

3. 修复后的效果

完成上述修改后:

  • 点击加减按钮修改counter时,DisplayName不会再无意义地重新渲染
  • 只有当输入框的name实际变化时,DisplayName才会更新并打印日志
  • 之前的无限循环问题彻底解决

备注:内容来源于stack exchange,提问作者Amit Agrawal

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 12:30:31