封装一个自动防重提交的 Hook

封装一个自动防重提交的 Hook

孤独的哈士奇 0 2026-03-25

之前提交表单都要写 loading = truedisabled = true.finally(() => loading = false)
感觉像是在重复造轮子。

比如有个业务逻辑是:

  • 用户点击“提交订单”

  • 点击“发送验证码”

  • 点击“保存设置”

每次我的写法都是:

  1. 定义一个 loading 状态;

  2. 在点击时设为 true

  3. 禁用按钮;

  4. 发起请求;

  5. 成功或失败后,再设回 false

一段逻辑,复制粘贴十次。

更糟的是——一旦忘记写 .finally,按钮就永远禁用;一旦并发请求没处理好,照样重复提交。

现在直接封装好一个自定义 Hook 每次碰到这种情景直接复制 下面是react的写法:

const [handleSubmit, isSubmitting] = useSubmitLock(async (formData) => {
  await api.submitOrder(formData);
  message.success('下单成功!');
});

return (
  <button disabled={isSubmitting} onClick={() => handleSubmit(data)}>
    {isSubmitting ? '提交中...' : '立即下单'}
  </button>
);
import { useState, useCallback } from 'react';

type AsyncFunction<T extends any[], R> = (...args: T) => Promise<R>;

export const useSubmitLock = <T extends any[], R>(
  asyncFn: AsyncFunction<T, R>
) => {
  const [isLocked, setIsLocked] = useState(false);

  const wrappedFn = useCallback(
    async (...args: T): Promise<R | undefined> => {
      if (isLocked) {
        console.warn('操作正在进行中,请勿重复提交');
        return; // 直接拦截,不执行函数
      }

      setIsLocked(true);
      try {
        const result = await asyncFn(...args);
        return result;
      } finally {
        setIsLocked(false); // 无论成功失败,一定解锁
      }
    },
    [isLocked, asyncFn]
  );

  return [wrappedFn, isLocked] as const;
};