usePrevious
用于保存上一次渲染时的状态。
React
官方文档提供了一个实现:
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
usePrevious
记录的值初始为空,每轮渲染后记录状态值,这样每次渲染返回的便是上一轮渲染时的值。
react-use
同样使用了此实现。
ahooks
则为用户提供了compare
,可以让用户决定是否更新usePrevious
记录的值。
import { useRef } from 'react';
export type compareFunction<T> = (prev: T | undefined, next: T) => boolean;
function usePrevious<T>(state: T, compare?: compareFunction<T>): T | undefined {
const prevRef = useRef<T>();
const curRef = useRef<T>();
const needUpdate = typeof compare === 'function' ? compare(curRef.current, state) : true;
if (needUpdate) {
prevRef.current = curRef.current;
curRef.current = state;
}
return prevRef.current;
}
export default usePrevious;
ahooks
使用了两个ref
,一个记录当前值,一个记录之前的值。不过为什么要这样实现呢?这样实现与react-use
的实现方式有什么不同?🤔
在一番试验无果后,随意搜索了下却找到了这个issue
什么?ahooks
的实现不符合使用规范😯
在issue
作者给出的链接中说明了在render
时读取或修改ref
的值时会进行警告,并且Dan在回复中说明了这样做的原因。
大意是在render
时读取ref
的值和读取一个随机的全局变量一样。读取的值是什么取决于何时调用render。如果React
调用在稍微不同的时间渲染,可能会得到不同的结果。
在未来React
默认开启Concurrent模式后,ahooks
的实现便会出现问题。
issue
作者除了给出解释之外,还提供了一个demo。demo中使用的usePrevious
在StrictMode
下有了不同的行为。
不过demo中渲染用的也是legacy
模式,那为什么在StrictMode
下行为会不同?🤔
打断点调试了一番,发现进入页面时usePrevious
居然被调用了两次,导致curRef
和preRef
记录的状态出现了问题。
在React issue中搜索StrictMode
、twice
等关键字找到了原因,还是我们的Dan神回复的:
使用了StrictMode
且用了Hooks
的组件会在开发模式时渲染两次。StrictMode
的一个主要目的是方便将现有项目迁移到未来使用concurrent
模式的React版本中,会这么设计不奇怪。
至此,作战告捷😊
简单改了下ahooks
的usePrevious
实现。
function usePrevious<T>(state: T, compare?: (prev: T | undefined, next: T) => boolean): T | undefined {
const ref = useRef<T>();
useEffect(() => {
const needUpdate = typeof compare === 'function' ? compare(ref.current, state) : true;
if (needUpdate) {
ref.current = state;
}
});
return ref.current;
}