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

 更新时间:2026年03月25日 09:15:13   作者:前端Hardy  
在日常开发中,我们需要无数次面对防止用户重复提交的需求,这篇文章主要为大家详细介绍了如何通过Vue自定义封装一个HOOK,实现自动防重复提交功能,有需要的小伙伴可以了解下

每次提交表单都要写 loading = truedisabled = true.finally(() => loading = false)

你不是在写业务,你是在重复造轮子。

在日常开发中,我们无数次面对这样的场景:

  • 用户点击“提交订单”
  • 点击“发送验证码”
  • 点击“保存设置”

而为了防止重复点击,你不得不:

  • 定义一个 loading 状态;
  • 在点击时设为 true
  • 禁用按钮;
  • 发起请求;
  • 成功或失败后,再设回 false

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

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

今天,我们就用 一个自定义 Hook,彻底终结这种体力劳动。

手动管理 loading 的三大痛点

1.代码冗余

const [submitting, setSubmitting] = useState(false);

const handleSubmit = async () => {
  if (submitting) return;
  setSubmitting(true);
  try {
    await submitForm();
  } finally {
    setSubmitting(false); // 忘记这行?按钮就废了
  }
};

每个按钮都要写一遍,毫无意义。

2.无法天然防重

即使你写了 if (submitting) return,如果用户快速双击,在 setSubmitting(true) 异步更新前,两次点击仍可能触发两次请求。

3.状态分散,难以维护

多个按钮?多个表单?每个都要独立管理状态,逻辑割裂。

解法:封装一个useSubmitLockHook

我们要实现的效果:

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

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

一行调用,自动加锁、自动解锁、自动防重、自动透传参数!

实现原理:Promise 锁 + 状态同步

// React + TypeScript 版本(JS 可轻松转写)
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;
};

关键设计亮点

特性说明
闭包锁isLockedtrue 时,直接 return,不执行原函数
自动 finally 解锁即使接口报错、用户中断,也不会卡死
泛型支持完美透传参数和返回值类型
无副作用不依赖全局状态,每个调用独立隔离

使用场景全覆盖

场景 1:表单提交

const [submitForm, submitting] = useSubmitLock(api.createPost);

场景 2:发送验证码

const [sendCode, sending] = useSubmitLock(phoneApi.sendSmsCode);
// 按钮文案可结合倒计时:{sending ? '发送中...' : '获取验证码'}

场景 3:删除确认操作

const [confirmDelete, deleting] = useSubmitLock(api.deleteUser);
// 防止用户狂点“确定”导致多次删除

场景 4:组合多个异步操作

const [handlePay, paying] = useSubmitLock(async (orderId) => {
  await api.createPayment(orderId);
  await trackEvent('pay_clicked');
  window.location.href = '/payment';
});

注意事项 & 进阶建议

1.不要用于需要“取消”的操作

此 Hook 适用于“提交即不可逆”的场景。如果是上传、下载等可取消任务,应使用 AbortController

2.与防重 Token 不冲突

useSubmitLock前端体验层防护,后端仍需配合 Token 或幂等设计做最终校验。

3.Vue 用户怎么办?

同样可封装为 Composable:

// Vue 3 + Composition API
import { ref } from 'vue';

export function useSubmitLock(asyncFn) {
  const isLocked = ref(false);
  
  const wrappedFn = async (...args) => {
    if (isLocked.value) return;
    isLocked.value = true;
    try {
      return await asyncFn(...args);
    } finally {
      isLocked.value = false;
    }
  };

  return { execute: wrappedFn, isLocked };
}

使用:

const { execute: submit, isLocked } = useSubmitLock(api.submit);

更进一步:自动绑定到按钮?

你可以再封装一个 <SubmitButton> 组件:

const SubmitButton = ({ onClick, children, ...props }) => {
  const [handler, loading] = useSubmitLock(onClick);
  return (
    <button
      disabled={loading}
      onClick={handler}
      {...props}
    >
      {loading ? '处理中...' : children}
    </button>
  );
};

// 使用
<SubmitButton onClick={submitOrder}>提交订单</SubmitButton>

从此,防重提交,零成本集成。

结语

优秀的工程师,不是写更多代码,而是让重复的事不再发生

一个小小的 useSubmitLock,背后是对用户体验的尊重,对代码洁癖的坚持,更是对“DRY 原则”的践行。

下次当你又要写第 101 次 loading = true 时,停下来问问自己:“这事,能不能一次解决?”

把这个 Hook 加到你的工具库里,团队效率提升 10%。

到此这篇关于Vue封装一个自动防重复提交的Hook的文章就介绍到这了,更多相关Vue防重复提交内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 基于Vue实现平滑过渡的拖拽排序功能

    基于Vue实现平滑过渡的拖拽排序功能

    这篇文章主要介绍了vue实现平滑过渡的拖拽排序功能,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-06-06
  • VUE利用vuex模拟实现新闻点赞功能实例

    VUE利用vuex模拟实现新闻点赞功能实例

    本篇文章主要介绍了VUE利用vuex模拟实现新闻点赞功能实例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • vue多级多选菜单组件开发

    vue多级多选菜单组件开发

    这篇文章主要为大家分享了vue多级多选菜单组件开发案例,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-10-10
  • 详解Vue3中setup函数的使用教程

    详解Vue3中setup函数的使用教程

    在vue3版本中,引入了一个新的函数,叫做setup,引入他的原因总结一下主要原因是:为了使用组合式 API,setup函数是Composition 的入口。本文将详细介绍一下Vue3中setup函数的使用教程,感兴趣的可以了解一下
    2022-07-07
  • vue 如何获取视频第一帧

    vue 如何获取视频第一帧

    这篇文章主要介绍了vue 如何获取视频第一帧,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-10-10
  • vue实现todolist单页面应用

    vue实现todolist单页面应用

    这篇文章主要为大家详细介绍了vue实现todolist单页面应用,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-04-04
  • vue拖拽组件 vuedraggable API options实现盒子之间相互拖拽排序

    vue拖拽组件 vuedraggable API options实现盒子之间相互拖拽排序

    这篇文章主要介绍了vue拖拽组件 vuedraggable API options实现盒子之间相互拖拽排序克隆clone,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-07-07
  • Vue数据更新视图不更新的几种解决方案小结

    Vue数据更新视图不更新的几种解决方案小结

    这篇文章主要介绍了Vue数据更新视图不更新的几种解决方案小结,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-08-08
  • vue 弹出遮罩层样式实例

    vue 弹出遮罩层样式实例

    这篇文章主要介绍了vue 弹出遮罩层样式实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-07-07
  • Vue2.0用户权限控制解决方案

    Vue2.0用户权限控制解决方案

    这篇文章主要介绍了Vue2.0用户权限控制解决方法以及源码说明,一起学习下。
    2017-11-11

最新评论