原文:https://juejin.cn/post/697243...
作者:Tonychen
Infinite Chain Of Update
理论应用中有时候会碰到 Infinite Chain Of Update
这个报错,其实就是你的一段代码引发了「死循环更新」。上面咱们来看几个例子
依赖数组问题
比如说在应用 useEffect
时没有传入依赖数组
// count 会有限 + 1function App() { const [count, setCount] = useState(0) useEffect(() => { setCount(count + 1) })}
为什么说 count
会有限更新?这里的逻辑是这样的
- 组件更新
- 执行
useEffect
- 更新
count
并触发组件更新 - 执行
useEffect
- ……
解决办法很简略,只有给 useEffect
传一个空数组作为第三个参数,下次更新时 useEffect
便不会执行。
// 失常渲染function App() { const [count, setCount] = useState(0) useEffect(() => { setCount(count + 1) }, [])}
监听了被更新的值
这个算是老手 hooks
玩家常常会遇到、新手也有些头疼的问题。
- 案例1
useEeffect
中更新的 state
间接影响了被监听的变量,举个例子
function App() { const [obj, setObj] = useState({a: 0}) const {a} = obj useEffect(() => { setObj({ ...obj, a: 1 }) }, [a, obj])}
下面这段代码在理论运行的时候就会导致死循环,为什么呢?因为在 setObj
的时候扭转的是 obj
这个值,而 useEffect
监听了这个值,从而 导致了死循环……
怎么解决呢?因为是 obj
变动引起的 infinite loop
,那么其实只有不监听 obj
就没有这回事了
,这里能够利用一下 setState
的「回调函数」用法
function App() { const [obj, setObj] = useState({a: 0}) const {a} = obj; useEffect(() => { setObj((state) => ({ ...state, a: 1 })) }, [a])}
- 案例2
有时候你须要依据不同的「状态」来决定组件显示什么,那么通常就须要利用一个 state
来管制若干种「状态」的显示,从状态 1 到状态 2 的转化是异步的。一个简略的做法就是用 useEffect
来监听它。
如果说这个状态有一部分依赖内部传入,另外一部分依据这个内部传入的状态的变动来进行对应的解决。举个例子
export function App({outsider, wait}) { const [state, setState] = useState('INIT') useEffect(() => { // 依据 ousider 解决 state 的值 if (outsider === true) { setState('PENDING') } else { if (state === 'PENDING') { setTimeout(() => { setState('RESOLVED') }, wait) } } }, [outsider, state]) return ( // 依据 state 来渲染不同的组件/款式 )}
理论运行起来的话又是 infinite loop
了,可能你第一工夫我想的一样,就是采纳「案例1」的解法。然而留神,这里是有异步解决的,所以这里只能说是利用 useRef
来做一下简略的解决。
export function App({outsider, wait}) { const [state, setState] = useState('INIT') const stateRef = useRef(state) useEffect(() => { // 依据 ousider 解决 state 的值 if (outsider === true) { setState('PENDING') stateRef.current = 'PENDING' } else { if (stateRef.current === 'PENDING') { setTimeout(() => { setState('RESOLVED') stateRef.current = 'RESOLVED' }, wait) } } }, [outsider]) return ( // 依据 state 来渲染不同的组件/款式 )}
这样一来在 useEffect
中就不须要依赖 state
,而且可能依据 state
以后的值做出一些操作
小结
在写 hooks
的时候,须要常常留神代码中是否有依赖 state
且 setState
的中央,通常直觉上的写法是会带来 infinite loop
的。
获取不到最新的值
老手 hooks
常常会碰到这类问题,上面是一个简略的例子
import { useCallback, useEffect, useState } from "react";let timeout = null;export default function App() { const [count, setCount] = useState(0); const handleClick = useCallback(() => { setCount(count + 1); timeout = setTimeout(() => { console.log("timeout", count); setCount(count + 1); }, 1000); }, [count]); useEffect(() => { return () => { clearTimeout(timeout); }; }, []); return ( <div className="App"> <p>{count}</p> <button onClick={handleClick}>click me</button> </div> );}
运行之后你会发现,每次 console.log
打印进去的都是上一次 count + 1
前的后果,而这其实就和 useState
实现有关系了,这里仅截取源码中的一小部分实现
能够看出,从 useState
中解构进去的是原数据的值而非援用,所以在下面的例子中,在 setTimeout
里拿不到最新的 count
值。
参考资料
Setting State based on Previous State in useEffect - Its a trap!