Vue如何优雅的解决点击防重提交?

503 阅读1分钟

一个很常见的实际场景,form表单提交的按钮需要防重提交,不然数据库就2条数据,怎么通过组件级别来控制呢?正常按钮都有点击事件,并且都是promise,我们能否拦截click,等promise结束,再设置loading为false。element-ui是一个优秀的vue组件,以这个为例书写一个el-pro-button

查看源码

import { ref, defineComponent } from "vue";

function useClick(onClick: any) {
  const loading = ref(false);
  const isPromise = (obj: any) => {
    // eslint-disable-next-line @typescript-eslint/no-empty-function
    const AsyncFunction = (async () => {}).constructor;
    return obj instanceof Promise || obj instanceof AsyncFunction === true;
  };
  const handleClick = () => {
    const fn = onClick;
    if (!fn || loading.value) {
      return;
    }

    loading.value = true;

    const execFn = fn.call(null, event);

    if (isPromise(execFn)) {
      execFn.finally(() => {
        loading.value = false;
      });
    } else {
      // 延迟300ms,关闭loading
      setTimeout(() => {
        loading.value = false;
      }, 300);
    }
  };

  return {
    loading,
    handleClick,
  };
}

export default defineComponent({
  props: {
    onClick: Function,
  },
  setup(props, { slots, attrs }) {
    const { loading, handleClick } = useClick(props.onClick);
    const children = slots && slots.default && slots.default();
    return () => (
      <el-button {...attrs} loading={loading.value} onClick={handleClick}>
        {children}
      </el-button>
    );
  },
});

调用方法

<script lang="ts" setup>
import ElProButton from "./components/el-pro-button";
const sleep = (timer: number) =>
  new Promise((resolve) => setTimeout(resolve, timer));
const handleClick1 = async () => {
  await sleep(1000);
};
</script>

<template>
  <h2>普通按照展示</h2>
  <el-pro-button type="primary">test</el-pro-button>
  <h2>按钮带事件</h2>
  <el-pro-button type="primary" @click="handleClick1">test</el-pro-button>
</template>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
}
</style>

查看效果

2023-07-15 16.16.54.gif