我有接受函数作为参数的钩子。如果函数发生不必要的更改,则可能会导致额外的重新渲染。我想确保函数用
useCallback
包装。
即这是一个简化版本:
function useApi({ onSuccess }) {
if (process.env.NODE_ENV !== 'production' && !isUseCallback(onSuccess)) {
throw new Error();
}
useEffect(() => {
fetch(...)
.then(res => res.json())
.then(onSuccess);
}, [onSuccess]);
}
如果
onSuccess
发生不必要的更改,那么它将不必要地调用API。可以有isUseCallback
这个功能吗?我想我必须编写一个自定义的 useCallback
来包装 React 的 useCallback
。
您可以将回调包装在
useRef()
中,以确保 useEffect()
回调引用最新的 onSuccess
:
function useApi({ onSuccess }) {
const ref = useRef();
ref.current = onSuccess;
useEffect(() => {
fetch(...)
.then(res => res.json())
.then(val => ref.current(val));
}, []);
}
重要的是,您应编写
.then(val => ref.current(val))
而不是仅编写 .then(ref.current)
,以便在 ref.current
解析之前不会发生属性访问 fetch()
。这样 ref.current
等于最近传递给 onSuccess
的 useApi()
。
我使用 Typescript 解决了这个问题:
/** So we can require arguments passed to our custom hooks to be wrapped in a useCallback().*/
declare function useCallback<T extends (...args: any[]) => any>(
callback: T,
deps: ReadonlyArray<any>
): UseCallback<T>
declare module 'react' {
/** Doesn't exist after compilation. Only exists in TypeScript. */
export const UseCallbackSymbol: unique symbol
/** So we can require arguments passed to our custom hooks to be wrapped in a React `useCallback()`.*/
export type UseCallback<T extends (...args: any[]) => any> = T & {
[UseCallbackSymbol]: true
}
// Override the useCallback function
export function useCallback<T extends (...args: any[]) => any>(
callback: T,
deps: ReadonlyArray<any>
): UseCallback<T>
}
.
function useApi(onSuccess: UseCallback<() => any>): any;
useApi(() => {}); // Error
useApi(useCallback(() => {})); // No error
您可以使用 npm 包
useCallback
中的 rely-use-callback
代替原始 useCallback。
该包包含RelyCallback
类型,可用于类型检查
例如:
import { useCallback, RelyCallback } from 'rely-use-callback';
const handleClick = useCallback(() => {}, []);
...
const useSomethinkToDoOnEffect = (
todoFunction: RelyCallback<() => void>
) => {
useEffect(() => {
todoFunction()
}, [ todoFunction ]);
}
const MemoizedComponent:FC<{
onClick: RelyCallback<() => void>
}> = memo(() => <ChildComponent onClick={onClick} />)
有关动机的更多信息,您可以阅读https://github.com/vkruglikov/rely-use-callback#-motivation