vue+elementUI按钮:LoadingButton组件

1,317 阅读2分钟

一、背景

在平时的工作中,经常甚至说一定会遇到表单中的确定/提交按钮,需要加上loading状态,防止二次请求,以免造成服务器性能的浪费,甚至逻辑错误。

但是每次给按钮添加loading属性,通过isLoading(布尔值)来开启和关闭loading状态,实在是太麻烦了,而且多个按钮还要定义多个变量去控制。

基于此,希望封装一个组件能够自带loading状态,当点击按钮发送请求时,显示加载状态,当请求返回时,关闭加载状态。

二、思路

在使用antd的modal组件时,发现onOk异步函数时,此时确定按钮会自动打开loading效果,函数执行完后关闭弹框

动画.gif

可以看到,他的异步函数是返回了一个promise

        onOk() {
          return new Promise((resolve, reject) => {
            setTimeout(Math.random() > 0.5 ? resolve : reject, 1000);
          }).catch(() => console.log('Oops errors!'));
        },

所以,如果封装一个Button组件,将需要执行的函数传入,如果这个函数是异步的,就自动打开loading效果,函数执行完关闭loading效果;否则就不打开loading效果

为什么返回一个promise就可以打开loading

通过源码可以看到,通过判断有没有then方法来确定传入的函数是否为异步函数

关键代码:ret && ret.then

    // node_modules/ant-design-vue/es/modal/ActionButton.js
    onClick: function onClick() {
      var _this2 = this;

      var actionFn = this.actionFn,
          closeModal = this.closeModal;

      if (actionFn) {
        var ret = void 0;
        if (actionFn.length) {
          ret = actionFn(closeModal);
        } else {
          ret = actionFn();
          if (!ret) {
            closeModal();
          }
        }
        if (ret && ret.then) {
          this.setState({ loading: true });
          ret.then(function () {
            // It's unnecessary to set loading=false, for the Modal will be unmounted after close.
            // this.setState({ loading: false });
            closeModal.apply(undefined, arguments);
          }, function (e) {
            // Emit error when catch promise reject
            // eslint-disable-next-line no-console
            console.error(e);
            // See: https://github.com/ant-design/ant-design/issues/6183
            _this2.setState({ loading: false });
          });
        }
      } else {
        closeModal();
      }
    }

三、封装组件

components/LoadingButton/index.vue

<template>
  <el-button :loading="loading" @click="handleClick" v-bind="$attrs">
    <slot></slot>
  </el-button>
</template>

<script>
export default {
  props: { onOk: { type: Function, default: () => {} } },
  data() {
    return { loading: false };
  },
  methods: {
    handleClick() {
      const ret = this.onOk();
      // 如果是异步函数,则显示loading
      if (ret && ret.then) {
        this.loading = true;
        ret
          .catch((err) => {
            console.warn(err);
          })
          .finally(() => {
            this.loading = false;
          });
      }
    },
  },
};
</script>

全局注册:

import LoadingButton from '@/components/LoadingButton'

Vue.component('LoadingButton', LoadingButton)

使用:

<template>
  <LoadingButton :onOk="onOk" type="primary">提交</LoadingButton>
</template>

  methods: {
    onOk() {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve();
        }, 1000);
      });
    },
  },

使用区别:

  1. 当使用el-button时,需要传loading,这里不需要传了
  2. 当使用el-button时,需要添加click事件,这里替换为onOk属性,属性是一个函数,并且该函数必须返回一个有状态的promise

效果:

动画.gif

实际使用时应该是请求接口的:

    onOk() {
      return new Promise((resolve, reject) => {
        axios.get(this.url) // http://api-hmugo-web.itheima.net/api/public/v1/home/swiperdata
          .then((res) => {
            console.log("数据请求结果", res);
            resolve();
          })
          .catch((err) => {
            reject(err);
          });
      });
    },

效果:

动画.gif

四、另一种方式封装

<template>
  <el-button :loading="loading" v-bind="$attrs" @click="handleClick">
    <slot />
  </el-button>
</template>
<script>
export default {
  name: 'LoadingButton',
  props: { autoLoading: { type: Boolean, default: true }, needEvent: { type: Boolean, default: false } },
  data() {
    return { loading: false }
  },
  methods: {
    handleClick(event) {
      if (this.autoLoading) {
        this.loading = true
      }
      if (this.needEvent) {
        this.$emit('click', event, () => {
          this.loading = false
        })
      } else {
        this.$emit('click', () => {
          this.loading = false
        })
      }
    }
  }
}
</script>

触发click时,将event传递,目的是为了父组件中可以使用stop等修饰符(不支持native

使用:

    <LoadingButton type="primary" @click.stop="handleSubmit">提交按钮</LoadingButton>
    
    // methods
    async handleSubmit(e, done) {
      const res = await axios.get('http://api-hmugo-web.itheima.net/api/public/v1/home/swiperdata');
      console.log("返回结果", res);
      done();
    },

stop修饰符根据实际使用场景来选择写不写

4.1 记一次修饰符失效的场景-20241018

vue2.6.14版本中,click事件添加上stop、prevent等修饰符,都是没问题的,但是在2.6.10版本上,只要添加上修饰符,后面的参数就无法传递给父组件中

2.6.14版本中,可以看到event参数后面带上了剩余参数:

image.png

2.6.10版本,在处理完stop后,只传了event

0ecd363712fbef19a68ecc1e4736a84.png

prevent也是一样: image.png

查阅vue版本记录可知:

2.6.13版本修复了这个问题

ff1efcdc0e0cfd2cdc2e25ac54e8bde.png

代码提交记录:

image.png