- Published on
useMemoizedFn
- Authors

- Name
- 李丹秋
useCallback的问题在于,只要依赖项发生了改变,函数还是会发生变化,而useMemoizedFn返回的函数永远不会变。其实理论上useMemoizedFn可以替代useCallback
示例
import { useMemoizedFn } from 'ahooks';
import { message } from 'antd';
import React, { useCallback, useRef, useState } from 'react';
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 (
<>
<p>count: {count}</p>
<button
type="button"
onClick={() => {
setCount((c) => c + 1);
}}
>
Add Count
</button>
<p>You can click the button to see the number of sub-component renderings</p>
<div style={{ marginTop: 32 }}>
<h3>Component with useCallback function:</h3>
{/* use callback function, ExpensiveTree component will re-render on state change */}
<ExpensiveTree showCount={callbackFn} />
</div>
<div style={{ marginTop: 32 }}>
<h3>Component with useMemoizedFn function:</h3>
{/* use memoized function, ExpensiveTree component will only render once */}
<ExpensiveTree showCount={memoizedFn} />
</div>
</>
);
};
// some expensive component with React.memo
const ExpensiveTree = React.memo<{ [key: string]: any }>(({ showCount }) => {
const renderCountRef = useRef(0);
renderCountRef.current += 1;
return (
<div>
<p>Render Count: {renderCountRef.current}</p>
<button type="button" onClick={showCount}>
showParentCount
</button>
</div>
);
});
源码
import { useMemo, useRef } from 'react';
type noop = (this: any, ...args: any[]) => any;
type PickFunction<T extends noop> = (
this: ThisParameterType<T>,
...args: Parameters<T>
) => ReturnType<T>;
const isDev = process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test';
const isFunction = (value: unknown): value is (...args: any) => any =>
typeof value === 'function';
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;
}
export default useMemoizedFn;