- 作者:老汪软件技巧
- 发表时间:2024-09-03 04:02
- 浏览量:
前言
在项目开发中,当页面请求接口时,组件局部或者页面全局显示loading加载遮罩可谓是司空见惯了,下面来讨论一下如何优雅的使用loading状态。下文引用了比较常用的两个组件Button按钮和Table表格作为例子,谈谈如何优雅的维护loading加载状态。
Butoon组件原始加载用法
<div>
<div>数据:{{ list }}div>
<el-button type="primary" @click="asyncFetch" :loading="loading">异步获取el-button>
<el-button type="primary" @click="syncFetch" :loading="loading">同步获取el-button>
div>
<script setup>
import { ref } from 'vue'
import { FetchApi } from '@/api'
const loading = ref(false)
const list = ref([])
// 异步调用
const asyncFetch = () => {
loading.value = true
FetchApi().then(res => {
console.log(res)
list.value = res
}).finally(() => {
loading.value = false
})
}
// 同步调用
const syncFetch = async () => {
loading.value = true
try {
const res = await FetchApi()
console.log(res)
list.value = res
} catch (error) {
console.log(error)
}
loading.value = false
}
script>
由以上代码可看出,在每次点击按钮提交时,都需要在调用接口前开启加载状态,在接口调用结束时,无论成功与否,都结束加载状态。为了简化这一操作步骤,减少代码冗余,我们可以封装一个button组件,把loading状态及相关逻辑提取这个组件当中。
封装Button组件
<el-button v-bind="$attrs" :loading="loading" :onClick="handleClick">
<slot>slot>
el-button>
<script setup>
import { ref } from 'vue'
const { onClick } = defineProps(['onClick'])
const loading = ref(false)
// 重写点击方法
const handleClick = async () => {
loading.value = true
try {
// 传递的onClick方法必须为同步的
onClick && await onClick()
} catch (error) {
console.error(error)
}
loading.value = false
}
script>
以上对button组件进行二次封装,把一些重复的业务逻辑放到该组件中,为了能够知道异步方法什么时候执行结束,将点击方法通过prop参数的形式传递到子组件中,交给子组件在合适的时机执行,学过react的话应该对此操作比较熟悉。
组件加载用法
Page.vue
<div>
<div>数据:{{ list }}div>
<ViButton type="primary" :onClick="syncFetch">同步获取ViButton>
div>
<script setup>
import { ref } from 'vue'
import { FetchApi } from '@/api'
import ViButton from '@/components/ViButton/ViButton.vue'
const list = ref([])
// 同步调用
const syncFetch = async () => {
const res = await FetchApi()
list.value = res
}
script>
需要注意的是,我们需要把FetchApi()异步方法转为同步,这是为了在Button组件中能够方便地知道FetchApi方法的执行结束时机,否则在Button组件中将不能正确的隐藏loading状态。
封装表格组件
<el-table v-bind="$attrs" v-loading="loading">
<slot>slot>
el-table>
<script setup>
import { ref, onMounted } from 'vue'
const props = defineProps({
// 获取数据方法
fetchData: {
type: Function,
default: () => {}
},
// 默认是onMounted立即执行fetchData
immediateFetch: {
type: Boolean,
default: true
}
})
const loading = ref(false)
const getList = async () => {
loading.value = true
try {
await props.fetchData()
} catch (error) {
console.log(error)
}
loading.value = false
}
onMounted(() => {
props.immediateFetch && getList()
})
// 暴露经过loading处理的fetch方法,比如搜索时候需要在父组件手动调用
defineExpose({ fetchData: getList })
script>
组件加载用法
page.vue
<div>
<ViTable ref="ViTableRef" :data="list" :fetchData="syncFetch" :immediateFetch="false">
<el-table-column prop="name" label="姓名">el-table-column>
ViTable>
<el-button @click="onSearch">查询el-button>
div>
<script setup>
import { ref } from 'vue'
import { FetchApi } from '@/api'
import ViTable from '@/components/ViTable/ViTable.vue'
const list = ref([])
const ViTableRef = ref(null)
// 同步调用
const syncFetch = async () => {
console.log('syncFetch')
const res = await FetchApi()
list.value = res
console.log(list.value)
}
// 搜索
const onSearch = () => {
ViTableRef.value.fetchData()
}
script>
以上需要注意的有两点:
数据是否需要在表格挂载完成立即获取;需要对外暴露经过loading加工的fetch方法,目的是满足需要手动调用获取数据的需求,比如点击搜索;
以上方式当需要手动调用时似乎还是有些麻烦,下面我们改成useHook方式来试试~
封装useLoading钩子函数
useLoading.js
import { ref } from 'vue'
// 多个组件共享统一状态
const loading = ref(false)
export const useLoading = (prop = {
fetchData: () => {}
}) => {
// 对外暴露loading给table组件或者button组件使用
// 内部只负责处理业务逻辑,不负责调用,这样更加灵活
return {
loading,
fetchData: async () => {
loading.value = true
try {
await prop.fetchData()
} catch (error) {
console.log(error)
}
loading.value = false
}
}
}
ViTablePlus.vue
<el-table v-bind="$attrs" v-loading="loading">
<slot>slot>
el-table>
<script setup>
import { useLoading } from '@/hooks/useLoading'
const { loading } = useLoading()
script>
用法
Page.vue
<div>
<ViTablePlus :data="list" :fetchData="syncFetch" :immediateFetch="false">
<el-table-column prop="name" label="姓名">el-table-column>
ViTablePlus>
<el-button @click="onSearch">查询el-button>
div>
<script setup>
import { ref, onMounted } from 'vue'
import { FetchApi } from '@/api'
import ViTablePlus from '@/components/ViTablePlus/ViTablePlus.vue'
import { useLoading } from '@/hooks/useLoading'
// 手动执行经过loading处理的fetchData方法
const { fetchData } = useLoading({
fetchData: syncFetch
})
const list = ref([])
// 需要使用function定义方法,进行变量提升,因为useLoading在syncFetch方法定义前执行
async function syncFetch() {
const res = await FetchApi()
list.value = res
}
// 搜索
const onSearch = () => {
// 经过loading处理的fetchData方法
fetchData()
}
onMounted(() => {
// 经过loading处理的fetchData方法
fetchData()
})
script>
通过使用useLoading钩子函数,我们轻松实现了在onMounted和onSearch方法中手动调用经过loading处理的fetchData方法!
总结
本文讲了在二次封装的Button和Table组件内部维护loading状态,为了能够获取到异步方法的调用完成时机,我们把异步方法转为同步方法后,通过prop的方式传递到子组件,委托子组件在恰当的时机执行。但把loading状态封装到组件内部还是不够灵活,最后使用useLoading钩子函数维护一个全局状态,提供灵活的跨组件状态共享。
孰是孰非,欢迎大家评论区进行讨论~