原文:https://juejin.cn/post/697243...
作者:Tonychen
Infinite Chain Of Update
实际使用中有时候会碰到 Infinite Chain Of Update
这个报错,其实就是你的一段代码引发了「死循环更新」。下面我们来看几个例子👇
依赖数组问题
比如说在使用 useEffect
时没有传入依赖数组👇
// count 会无限 + 1
function 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!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。