为什么情况1和无限循环,而情况2不是,因为useState()
中的obj
不是?
情况1:无限循环
const [count, setCount] = useState(0)
const [obj,setObj] = useState({a:1, b:2})
useEffect(( )=>
{setCount((prevCount) => prevCount + 1)
},[{a:1, b: 2 }])
情况2:不是无限循环
const [count, setCount] = useState(0)
const [obj,setObj] = useState({a:1, b:2})
useEffect(( )=>
{setCount((prevCount) => prevCount + 1)
},[obj])
React在第一个例子中使用引用等式来检查依赖关系是否发生了变化,每次创建新对象时,引用都会发生变化,组件会重新发布。
在第二种情况下,状态保持对象的引用,该引用在转发器之间保持不变,因此不会再次触发useEffect。
引物:
- React在每次重新渲染时逐行运行整个函数。从本质上讲,所有的变量和函数都是用全新的内存地址重新初始化的。然而,有一个问题,来自React函数的变量和函数不会用新地址重新初始化,例如
useState
、useCallback
(只有当其依赖关系发生变化时才会重新运行(等 - 只要任何状态发生变化,React就会重新渲染
- 为了检测是否有任何状态发生了变化,React在前一个状态和当前状态之间运行相等性检查。根据状态的类型(
String
、Number
、Object
(,检查的行为不同- 当将
Number
状态与===
状态进行比较时,将比较它们的值。注意,这是一个恒定时间O(1)
操作。例如,如果previousState = 5
和currentState = 6
,React发出状态变化的信号 - 考虑具有
previousState = {a: 1, b: 2}
和currentState = {a: 2, b: 1}
的Object
状态。为了了解这两种状态是否不同,必须检查每把钥匙才能得出结论。然而,请注意,对于平面对象来说,这是一个昂贵的操作,具有~O(keys)
的时间复杂性(想想深度嵌套的对象及其所需的时间?!(。由于React必须经常运行相等性检查,因此它避免了此操作,而是进行引用检查。现在,这意味着什么,它如何更快?JavaScript中的每个对象都有一个内存地址(也称为引用(。React根据两个对象在恒定时间O(1)
中对===
的引用来检查它们是否相等
- 当将
- 每次创建对象时,都会为其分配一个新的内存地址(引用(
为什么存在无限循环
在第一个示例中,useEffect
第一次运行并更新count
状态。这将导致重新渲染。在重新渲染中,每一行都会再次执行。当涉及到useEffect
时,React基于其相等性检查将先前的{a: 1, b: 2}
与当前的{a: 1, b: 2}
进行比较。由于这两个对象都是动态创建的,它们都有不同的地址,因此React认为依赖关系已经改变,并重新渲染组件。这种情况持续不断,我们陷入了一个无限循环。
在第二个例子中,obj
,一个React状态,被传递给useEffect
。由于React在重新渲染中不会重新初始化useState
,因此对象永远不会重新初始化,内存地址(引用(也不会发生变化。因此,当React运行相等检查时,对象显示为相等,不会导致任何重新渲染。