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;
}
复制代码