React中DisplayName组件useEffect无依赖持续更新state导致重复渲染问题求助
问题描述
In the
DisplayNamecomponent the state keeps updating even though there is no change in state.console.log("Rendered")andconsole.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
DisplayNamecomponent, the state namedvalueshould only update once via theuseEffectfrom""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

