useMemoizedFn文档地址:https://ahooks.js.org/zh-CN/hooks/use-memoized-fn
在 React 中,自定义的 Hooks 内部的函数在以下常见的几种情况下会被重新赋值,导致更新引用:
组件重新渲染:
当组件重新渲染时,Hooks 内部的函数会被重新执行,从而导致函数的重新赋值和更新引用。
这意味着每次组件重新渲染时,Hooks 内部的函数会被重新计算,返回新的函数引用。
依赖项发生变化(对于使用了依赖项的 Hooks):
对于某些 Hooks,例如 useEffect、useMemo 和 useCallback,当其依赖项发生变化时,Hooks 内部的函数也会被重新执行。
这会导致函数的重新赋值和更新引用,以确保在依赖项发生变化时能够获得最新的函数引用。
闭包内部依赖的状态发生变化:
自定义的 Hooks 可能使用闭包内部的状态或其他上下文中的值作为依赖,如果这些依赖的状态发生变化,Hooks 内部的函数也会重新执行。这样做是为了保持函数内部依赖的状态是最新的,避免出现闭包中使用过期数据的问题。
在 React 中,useMemoizedFn
和 useCallback
都用于优化函数组件的性能,但它们有一些不同点。
useMemoizedFn
: 这不是 React 的内置 Hook,而是可能由第三方库提供的功能。它的目的是将一个函数 “记忆化”,类似于 useCallback
,但在某些情况下,它提供了更多的功能,例如支持自定义的缓存策略和多个实例之间共享缓存等。useCallback
: 这是 React 的内置 Hook,专门用于记忆化函数。它的主要目的是在依赖项列表不变时,返回相同的函数引用,以减少函数的重新创建。useMemoizedFn
的基本思想是将一个函数包装起来,并根据特定的条件缓存函数的结果,以便在相同的输入参数下再次调用时,直接返回缓存的结果,而不必重新计算。useCallback
接受一个函数和依赖项列表,并在依赖项列表不变时返回记忆化的函数引用。import React, { useState, useCallback } from 'react';
import { message } from 'antd';
import { useMemoizedFn } from 'ahooks';
export default () => {
const [count, setCount] = useState(0);
const callbackFn = useCallback(() => {
message.info(`Current count is ${count}`);
}, [count]);
const memoizedFn = useMemoizedFn(() => {
message.info(`Current count is ${count}`);
});
return (
<>
count: {count}
>
);
};
在上面的示例中,callbackFn和memoizedFn效果是一样的,区别在于memoizedFn不用自己指定依赖。
使用 useMemo
来实现类似于 useCallback
的功能。事实上,useCallback
本质上就是使用 useMemo
来进行函数记忆化的一种简化形式。
useCallback
和 useMemo
都是 React 的内置 Hook,它们都用于在依赖项不变时,返回记忆化的值。唯一的区别是 useCallback
是专门用于记忆化函数,而 useMemo
可以用于任何类型的值。
在 React 组件中,当函数作为依赖项传递给子组件时,通常会使用 useCallback
来确保子组件不会因为父组件的重新渲染而频繁地创建新的函数。然而,你也可以使用 useMemo
来达到相同的目的。
下面是一个使用 useMemo
实现类似于 useCallback
的示例:
import React, { useState, useMemo } from 'react';
function useMyCallback(callback, deps) {
return useMemo(() => callback, deps);
}
const MyComponent = () => {
const [count, setCount] = useState(0);
// 使用 useMyCallback 记忆化函数
const handleClick = useMyCallback(() => {
console.log('Callback function called');
setCount(count + 1);
}, [count]);
return (
Count: {count}
);
};
在上面的示例中,我们定义了一个 useMyCallback
Hook,它接受一个函数 callback
和一个依赖项列表 deps
。在内部,我们使用 useMemo
将传入的 callback
进行记忆化,并返回记忆化的函数引用。
这样,当 count
不变时,handleClick
将返回相同的函数引用,避免了因为父组件重新渲染而导致的函数的频繁创建,从而实现了和 useCallback
类似的功能。
useMemoizedFn
是持久化 function 的 Hook,理论上,可以使用 useMemoizedFn 完全代替 useCallback。使用 useMemoizedFn,可以省略第二个参数 deps,同时保证函数地址永远不会变化。
function useMemoizedFn(func){
if(typeof func !== 'function') return
// 通过 useRef 保持其引用地址不变,并且值能够保持值最新
const funcRef = useRef(func)
funcRef.current = useMemo(()=>{
return func
}, [func])
const memoizedFn = useRef();
if (!memoizedFn.current) {
// 返回的持久化函数,调用该函数的时候,调用原始的函数
memoizedFn.current = function(...args){
return funcRef.current.apply(this, args)
}
}
return memoizedFn.current
}
ahooks源码实现如下:
import { useMemo, useRef } from 'react';
import { isFunction } from '../utils';
import isDev from '../utils/isDev';
function useMemoizedFn(fn) {
if (isDev) {
if (!isFunction(fn)) {
console.error("useMemoizedFn expected parameter is a function, got ".concat(typeof fn));
}
}
var fnRef = useRef(fn);
// why not write `fnRef.current = fn`?
// https://github.com/alibaba/hooks/issues/728
fnRef.current = useMemo(function () {
return fn;
}, [fn]);
var memoizedFn = useRef();
if (!memoizedFn.current) {
memoizedFn.current = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
return fnRef.current.apply(this, args);
};
}
return memoizedFn.current;
}
export default useMemoizedFn;
useMemoizedFn
的实现中使用了两个 useRef
钩子,具有不同的用途:
fnRef
:这个 useRef
用于存储原始函数(fn
)的引用。它被初始化为传递给 useMemoizedFn
函数的 fn
参数,并且其目的是始终保持函数的最新版本。使用它的原因是确保记忆化的函数(memoizedFn
)能够始终调用最新版本的原始函数 fn
,即使在渲染之间 fn
发生了变化。
memoizedFn
:这个 useRef
用于存储将由 useMemoizedFn
钩子返回的记忆化函数。其目的是在渲染间保持记忆化函数的引用。当 memoizedFn
首次创建时,它被赋值为一个新的函数,该函数调用最新版本的 fnRef.current
并传递提供的参数。然后,这个记忆化函数被返回并在组件中使用。
这样的实现通过使用两个独立的 useRef
钩子,确保记忆化函数能够正确地引用最新版本的原始函数,同时保持记忆化函数本身的稳定引用。
使用 useMemo
更新 fnRef.current
而不是直接赋值 fnRef.current = fn
,是为了确保当 fn
依赖项发生变化时,记忆化函数会被重新计算。通过在 useMemo
中使用 [fn]
作为依赖项数组,记忆化函数将始终引用 fn
的最新版本,并在 fn
发生变化时相应地更新。
这个实现旨在确保 useMemoizedFn
返回的记忆化函数保持高效,并正确地反映原始函数的最新版本和其依赖项的最新变化。
https://juejin.cn/post/7106061970184339464#heading-6
https://ahooks.js.org/zh-CN/hooks/use-memoized-fn