向自定义Hook传递函数时,该用memoization还是useCallback?
关于自定义Async Hook中异步函数的memoization处理问题
我写了一个接受异步函数作为参数的自定义Hook,它会调用该函数并在完成后返回结果。现在纠结传递这个函数时,该用memoization还是useCallback?
示例用法:
const { status, error, fire, data } = useAsyncOperation( () => { return Promise.resolve("Async result"); }, { autofire: true } );
我担心内联匿名函数每次渲染都会变化,导致Hook把它当成新值(哪怕逻辑完全一样),该怎么处理?
自定义Hook的实现代码:
import { useCallback, useEffect, useState } from 'react'; type UseAsyncOperationResult<TResult> = { status: 'idle' | 'pending' | 'success' | 'error'; fire: () => Promise<TResult>; error?: Error; data?: TResult; }; type UseAsyncOperationOptions<TResult> = { onSuccess?: (result: TResult) => void; onError?: (error: Error) => void; autofire?: boolean; }; export const useAsyncOperation = <TResult>( operation: () => Promise<TResult>, options: UseAsyncOperationOptions<TResult> = {} ): UseAsyncOperationResult<TResult> => { // TODO: use reducer to manage state in a cleaner way const [status, setStatus] = useState<'idle' | 'pending' | 'success' | 'error'>('idle'); const [error, setError] = useState<Error | undefined>(); const [data, setData] = useState<TResult | undefined>(); const [autofired, setAutofired] = useState(false); const { onSuccess, onError, autofire } = options; const fire = useCallback(async () => { setStatus('pending'); try { const result = await operation(); setStatus('success'); setData(result); if (onSuccess) onSuccess(result); return result; } catch (error) { setStatus('error'); setError(error as Error); if (onError) onError(error as Error); return Promise.reject(error); } }, [onError, onSuccess, operation]); useEffect(() => { if (autofire && !autofired) { console.log('autofire'); setAutofired(true); void fire(); } }, [autofire, fire, autofired]); return { status, fire, error, data, }; };
解决方案:用useCallback稳定你的异步函数
你的担心是对的——每次渲染时内联函数都会创建新的引用,这会导致:
fire函数因为依赖项operation变化而重新生成- 即便有
autofired状态控制只执行一次,fire的重复生成仍是不必要的性能损耗
处理方式分两种场景:
1. 异步函数无外部依赖
如果异步函数逻辑固定,不依赖组件内的变量或状态,直接用useCallback包裹,空依赖数组确保函数只创建一次:
const asyncOp = useCallback(() => { return Promise.resolve("Async result"); }, []); const { status, error, fire, data } = useAsyncOperation(asyncOp, { autofire: true });
2. 异步函数依赖组件内变量/状态
如果函数需要用到组件的state或props,把这些依赖项加入useCallback的依赖数组,确保依赖变化时函数才更新:
const [userId, setUserId] = useState(1); const asyncOp = useCallback(() => { return fetch(`/api/user/${userId}`).then(res => res.json()); }, [userId]); const { status, error, fire, data } = useAsyncOperation(asyncOp, { autofire: true });
额外优化:让Hook本身兼容函数变化
如果想让Hook对外部传入的operation变化更宽容,可以在Hook内部用useRef缓存最新的operation,这样即使operation变化,fire函数也不会频繁重新生成,同时保证调用的是最新逻辑:
export const useAsyncOperation = <TResult>( operation: () => Promise<TResult>, options: UseAsyncOperationOptions<TResult> = {} ): UseAsyncOperationResult<TResult> => { // 用ref缓存最新的operation const operationRef = useRef(operation); useEffect(() => { operationRef.current = operation; }, [operation]); // 原有状态定义 const [status, setStatus] = useState<'idle' | 'pending' | 'success' | 'error'>('idle'); const [error, setError] = useState<Error | undefined>(); const [data, setData] = useState<TResult | undefined>(); const [autofired, setAutofired] = useState(false); const { onSuccess, onError, autofire } = options; const fire = useCallback(async () => { setStatus('pending'); try { // 调用缓存的最新函数 const result = await operationRef.current(); setStatus('success'); setData(result); if (onSuccess) onSuccess(result); return result; } catch (error) { setStatus('error'); setError(error as Error); if (onError) onError(error as Error); return Promise.reject(error); } }, [onError, onSuccess]); // 不再依赖operation,改用ref缓存版本 useEffect(() => { if (autofire && !autofired) { console.log('autofire'); setAutofired(true); void fire(); } }, [autofire, fire, autofired]); return { status, fire, error, data, }; };
内容的提问来源于stack exchange,提问作者Steve B
相关产品推荐
相关产品推荐

