一、背景
在平时的工作中,经常甚至说一定会遇到表单中的确定/提交按钮,需要加上loading状态,防止二次请求,以免造成服务器性能的浪费,甚至逻辑错误。
但是每次给按钮添加loading属性,通过isLoading(布尔值)来开启和关闭loading状态,实在是太麻烦了,而且多个按钮还要定义多个变量去控制。
基于此,希望封装一个组件能够自带loading状态,当点击按钮发送请求时,显示加载状态,当请求返回时,关闭加载状态。
二、思路
在使用antd的modal组件时,发现onOk
为异步函数
时,此时确定按钮会自动打开loading效果,函数执行完后关闭弹框
可以看到,他的异步函数
是返回了一个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);
});
},
},
使用区别:
- 当使用el-button时,需要传loading,这里不需要传了
- 当使用el-button时,需要添加
click
事件,这里替换为onOk
属性,属性是一个函数,并且该函数必须返回一个有状态的promise
效果:
实际使用时应该是请求接口的:
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);
});
});
},
效果:
四、另一种方式封装
<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参数后面带上了剩余参数:
2.6.10版本,在处理完stop后,只传了event
prevent也是一样:
查阅vue版本记录可知:
2.6.13版本修复了这个问题
代码提交记录: