React中的 useCallback 和 ahooks 中的 useMemoizedFn
应该是一个比较常见的问题了,但是在我这段工作开始之前,我有两三年没有正儿八经写前端了,我的
React
经验还停留在React 16
, 基本上算一个新手,所以记录一下。
事情的起因是我在组件的 useEffect
中依赖了一个外部函数,当函数没有添加到依赖数组时,eslint 会报错 “mssing dependency”.
虽然报错级别是 warning
, 但是作为一个强迫症,我不能忍啊。啪,很快的,我把这个函数直接就丢到依赖数组里了,这下好了,我发现 useEffect
开始不停地触发。
这个问题通过搜索,不难找到答案,因为每次函数的地址都会发生修改,导致 useEffect
的依赖数组更新。结合搜索结果,我也很快想起来,有 useCallback
这个东西。但是我发现 useCallback
在我这个场景下好像用不了,因为我这个函数本身是引入的一个外部定义好的函数,放到 useCallback
里还是要求将函数传入依赖数组,不传eslint就会有 warning
. 作为一个强迫症,我不能忍啊。
后面找到解决方法,可以用 useRef
来解决。用 useRef
把这个函数套一下,在 useEffect
里用 ref.current
来调用就可以了,也不会要求传入依赖参数了。
这个问题到上面就解决完了。但是刚好我们的代码库里是有用到 ahooks, 有一天我在看文档的时候,发现有一个 useMemoizedFn
. 根据文档的描述:
持久化 function 的 Hook,理论上,可以使用 useMemoizedFn 完全代替 useCallback。 在某些场景中,我们需要使用 useCallback 来记住一个函数,但是在第二个参数 deps 变化时,会重新生成函数,导致函数地址变化。 使用 useMemoizedFn,可以省略第二个参数 deps,同时保证函数地址永远不会变化。
这恰恰是用来完美解决我的问题的,于是我就想知道它是怎么实现的,是不是也是通过 useRef
. 一看代码,确实是。
function useMemoizedFn<T extends noop>(fn: T) {
if (isDev) {
if (!isFunction(fn)) {
console.error(`useMemoizedFn expected parameter is a function, got ${typeof fn}`);
}
}
const fnRef = useRef<T>(fn);
// why not write `fnRef.current = fn`?
// https://github.com/alibaba/hooks/issues/728
fnRef.current = useMemo(() => fn, [fn]);
const memoizedFn = useRef<PickFunction<T>>();
if (!memoizedFn.current) {
memoizedFn.current = function (this, ...args) {
return fnRef.current.apply(this, args);
};
}
return memoizedFn.current as T;
}