You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

向自定义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稳定你的异步函数

你的担心是对的——每次渲染时内联函数都会创建新的引用,这会导致:

  1. fire函数因为依赖项operation变化而重新生成
  2. 即便有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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 15:45:36