原文: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 的时候,须要常常留神代码中是否有依赖 statesetState 的中央,通常直觉上的写法是会带来 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!