useEffect 引起的无限循环

自定义 hook

我们在使用 React Hock 时 为了逻辑复用经常来封装⼀个请求分⻚列表的⾃定义 hooks,写⼀个 hook 的核⼼就是处理:输⼊(使⽤ hook 时的变量)、输出(使⽤ hook 获取的值),以及哪些值可以被维护在 hook 内部,使⽤者可以⽆感知,⽽从输⼊到输出的过程,即可复⽤的逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
export function useFetchList(url, params) {
const [loading, setLoading] = useState(false);
const [pageNo, setPageNo] = useState(1);
const [data, setData] = useState({
totalCount: 0,
end: false,
records: []
});
useEffect(() => {
setLoading(true);
ajax
.get(url, {
...params,
pageNo
})
.then(({ result }) => {
setData(result);
})
.catch(handleError)
.then(() => {
setLoading(false);
});
}, [pageNo, params, url]);
return [{ loading, data }, setPageNo];
}

但是!!上述代码会引发⼀个问题——⽆限循环

引发原因:useEffect 的依赖对⽐,⽤的是 Object.is()
也就是说,上⾯的代码中,使⽤ { bookId } 作为参数时,每次渲染都是⼀个新的参数,会导致
useFetchList 不断去取数据,⽽取到数据后,⼜会引发函数组件重新渲染,导致 { bookId } 更新。

  • 解决⽅法 1:调⽤ hook 前,使⽤ useState 缓存 { bookId }
  • 解决⽅法 2:调⽤ hook 前,使⽤ useMemo 缓存 { bookId }
  • 解决⽅法 3:hook 内部判断 params 是否真的更新了
  • 解决⽅法 4:使⽤ useDeepCompareEffect(第三⽅库)替换 useEffect,但是谨慎使⽤,滥⽤可能会导致性能问题。

这⾥针对⽅法 3 做了实现,因为这是使⽤⽅可以最轻松的使⽤⽅法。

1
2
3
4
5
6
7
8
9
10
// 通过 useRef(⻅官⽅ Hook API 说明) 缓存值的引⽤,每次传⼊新值时,通过深对⽐进⾏⽐较,如果没
function useMemoizedValue(value) {
const cache = useRef(null);
const previousValue = cache.current;
if (_.isEqual(previousValue, value)) {
return previousValue;
}
cache.current = value;
return value;
}

这个 hook 终于可以被正常调⽤了