react 死循环
我以为如下代码会死循环 :
import { FC, useEffect, useState } from 'react';
interface HeadFormProps {
value: string;
onChange: (values: string) => void;
}
const HeadForm: FC<HeadFormProps> = (props) => {
const [state, _setState] = useState<string>('heh');
const [motto, setMotto] = useState('');
// 设置子组件状态,要同步到父组件
const setState = (value: string) => {
_setState(value);
if (props.onChange) {
props.onChange(value);
}
};
// 监听父组件传入值的变更,同步至子组件
useEffect(() => {
if (props.value) {
_setState(props.value);
}
}, [props.value]);
return (
<div className="head-form">
<div className="form-groups">
<div className="form-group flex gap-1">
<label htmlFor="name">姓名</label>
<input className="border" type="text" value={state} onChange={(e) => setState(e.target.value)} />
</div>
<div className="form-group flex gap-1 mt-2">
<label htmlFor="name">签名</label>
<input className="border" type="text" value={motto} onChange={(e) => setMotto(e.target.value)} />
</div>
</div>
</div>
);
};
const DemoTwo = () => {
const [searForm, setSearForm] = useState<string>('xixi');
return (
<div className="wrapper">
<HeadForm value={searForm} onChange={setSearForm} />
<div>{JSON.stringify(searForm)}</div>
</div>
);
};
export default DemoTwo;
但实际上却没有!
代码分析
useEffect(() => {
if (props.value) {
_setState(props.value); // 这里设置子组件的state
}
}, [props.value]); // 依赖props.value
为什么没有死循环
-
React的状态更新机制:
- 当您在子组件中调用
setState(e.target.value)
时,确实会触发父组件的onChange
- 父组件的
setSearForm
会更新状态,导致父组件重新渲染 - 父组件重新渲染时,会传递新的
value
给子组件
- 当您在子组件中调用
-
但不会死循环的原因:
- React会对状态更新进行批处理,不会立即触发渲染
- 当
_setState(props.value)
被调用时,如果props.value
与当前state
相同,React会跳过重新渲染 - 基本类型的值比较是值比较,只有当值真正改变时才会触发更新
-
具体流程:
- 用户输入 → 子组件setState → 触发onChange → 父组件setSearForm
- 父组件渲染 → 传递新的value给子组件
- 子组件useEffect检测到props.value变化 → 但_setState()发现值相同 → 跳过更新
可能产生死循环的情况
只有在特定情况下才会产生死循环:
useEffect(() => {
// 如果这里总是设置一个不同的值
_setState(props.value + " "); // 每次添加空格,值总是不同
}, [props.value]); // 这样会导致无限循环
您的代码没有死循环是因为React的优化机制和值比较策略阻止了不必要的渲染。
也就是说如果传入的值和设置的值相等,则不会引起再次 render!
const [count, setCount] = useState<number>(1);
useEffect(()=>{
setCount(1);
console.log('执行了');
},[count])
如果不一样,则会
const [count, setCount] = useState<number>(1);
useEffect(()=>{
setCount(count+1);
console.log('执行了');
},[count])
在react同一次渲染周期中,多次设置值则会被合并为一次 render 更新
让我解释什么情况属于"同一次渲染周期"。
同步代码块中的多次状态更新
const Example = () => {
const [count, setCount] = useState(0);
const handleClick = () => {
// 这些都属于同一次渲染周期
setCount(1); // 第一次更新
setCount(2); // 第二次更新
setCount(3); // 第三次更新
// React会批量处理,最终只使用最后一次setCount(3)
};
return <button onClick={handleClick}>Count: {count}</button>;
};
您的代码具体分析
以下代码不属于同一周期,但是依然被优化了
先处理批次1:子组件使用新state("a")重新渲染
再处理批次2:父组件使用新state("a")重新渲染 → 传递新的props.value = "a"
跨组件的状态更新会产生多个渲染周期
_setState("a")和props.onChange("a")属于同一次事件循环但不同的渲染批次
React会批量处理同步代码中的多个setState,但跨组件的状态更新会产生多个渲染周期
最后的_setState("a")因为值相同而被React优化掉,避免了无限循环
const setState = (value: string) => {
_setState(value); // 状态更新1
if (props.onChange) {
props.onChange(value); // 触发父组件状态更新
}
};
// 在输入框onChange中:
onChange={(e) => setState(e.target.value)}
- 用户输入"a":
setState("a")
→_setState("a")
+props.onChange("a")
- 父组件处理:父组件
setSearForm("a")
,安排重新渲染 - 父组件渲染:传递新的
value="a"
给子组件 - 子组件useEffect:检测到
props.value
从""变为"a" - 子组件_setState("a"):但当前state已经是"a",React跳过更新
产生死循环的例外情况
只有在每次设置不同值时才会循环:
useEffect(() => {
// 错误示例:总是设置不同的值
_setState(props.value + Math.random()); // 每次值都不同
}, [props.value]);
您的代码能正常工作,正是因为React的智能批处理和相同值检测机制。
React不仅在同一渲染周期内会优化相同值的状态更新,在跨渲染周期的情况下也会进行优化
// 子组件内部state已经是"a"
useEffect(() => {
if (props.value) { // props.value = "a"
_setState(props.value); // 尝试设置state为"a"(与当前值相同)
}
}, [props.value]); // props.value从""变为"a"
// React比较发现:currentState("a") === nextState("a")
// ✅ 跳过此次状态更新,避免不必要的重新渲染
React的优化策略
同一周期内:批量处理多个setState,只使用最后一个值.
跨周期:比较新旧值,如果相同则跳过渲染.
深度优化:即使是不相关的渲染触发,也会进行值比较.
React setState 的内部行为
当新旧值相等时,setState 不会执行任何有效工作,直接返回。
这才是不会触发 render 的核心吧,虽然 setXX 执行了,可是内部代码却跳出更新,因为值相等!