【手把手】Element UI&Plus里Loading的极致封装✨!只需0.5行超简洁使用
「这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战」
📖阅读须知,阅读本文你将学会以下知识:
1. 顺便复习下ElLoading的常规用法
2. 如何一步步将业务页面中常见的长达10行
的代码块,通过封装浓缩成0.5行
(不骗人)
3. 初步了解函数式编程
思路
一、好用的ElLoading
作为Element-UI
和Element-Plus
的忠实迷弟,我已经深度使用它们四年之久。
客观来评价,ElLoading
组件完全配得上简单易用
四个字。
用法一:v-loading指令模式
<div v-loading="true">
<h1>Hello</h1>
<p>World</p>
</div>
效果:
用法二:ElLoading.service服务模式
const loadingInstance = ElLoading.service({text: '转一转' })
setTimeout(() => {
// 关闭全局Loading
loadingInstance.close()
}, 1000)
效果:
尤其是上面这种用法二,
函数调用的形式
做到了可以不定义响应式对象
,降低页面逻辑复杂程度,是业务开发的利器之一。
二、最常见的ElLoading使用方式
如果你也经常写业务,那下面这段代码你一定不陌生吧🙂:
let loading
try {
loading = ElLoading.service({
lock: true,
text: '转一转',
background: 'rgba(0, 0, 0, 0.1)'
})
await queryXXXX(params)
loading.close()
} catch(err) {
throw(err)
loading.close()
}
发现问题的关键所在了吗?
2.1 问题一:占用的篇幅实在太长了!!
好家伙,细细一数,仅仅是和loading相关的代码,就有足足6行
,而且为了关闭这个loading,有时候我们还得再花4行
来写:
try {
} catch(err) {
throw(err)
}
这样算来,经常我们仅仅是为了实现"await queryXXXX(params)时会有全屏loading效果"
这么一件微不足道的事情,花费了整整4-10行代码。
如果当这样的逻辑在代码里更多时,我们甚至可能需要在一个.vue文件内花费几十行
的代码处理这件事。
2.2 问题二: 粗心的同事狠可怕
上图这种写法虽然需要不停地+10行、+10行、+10行
,但这并不是这种写法最可怕的地方。
如果你有一个同事,他不仅对代码没追求
,对稳定性
和质量
也没追求,经常写出如下这种代码:
const loading = ElLoading.service({text: '转一转' })
await queryXXXX(params)
loading.close()
好家伙,我直接好家伙。
一旦queryXXXX(params) throw ERROR
,你的整个前端项目都会被loading永远镇压,只能无奈按F5刷新页面了。
2.3 问题三:需要不停地传入一模一样的配置
Element-UI和Element-Plus是面向所有后端业务的开发库,因此它需要支持各种各样的场景,和你的业务场景
也许并不完全贴合。
比如,在我的业务中,我总是需要传入如下配置:
ElLoading.service(
{
lock: true,
text: '正在加载',
background: 'rgba(0, 0, 0, 0.1)'
}
)
万一你司原来的UI同学离职了,来了个审美完全不同的新UI,好家伙,整个项目改几百个地方的配置....想想就酸爽。
三、动手封装ElLoading
如果你是一个没有代码洁癖
,且认为代码能运行就行
的同学,那么故事到此也就结束了。
但我相信屏幕前的你,也一定和我一样,对如何更优雅、更高效
的完成工作有更高的追求,那就一定会对目前这种常见+10行、+10行、+10行
且伴随着定时炸弹
的写法表示不满的。
开始封装吧!
3.1 封装痛点一:如何解决代码行数太多的问题
?
对此,我的思路是使用函数式编程
,将所有的细节隐藏到函数内部。
设想中封装后的使用效果如下:
import withLoading from './loading'
// 将await queryXXXX(params)变成下方写法
await withLoading(queryXXXX)(params)
// 嘿嘿,这一行关于loading的逻辑,说自己是0.5行没毛病吧
估计有一些对函数式编程
不太熟悉的同学,可能看到这行代码直接就要愣一秒,并且直接变成"问号脸.jpg"。
那么我换个写法,也许能更清晰一点:
// 将之前queryXXXX变成一个新的方法
const queryXXXWithLoading = withLoading(queryXXXX);
// 再按以前的方式来调用它
await queryXXXWithLoading(params)
这里不得不感叹一句,函数式编程
的思想实在是太棒了,针对函数本身进行编程
的思路实在是绝中绝。
那应该怎么实现上面withLoading
的效果呢?如下:
// 传入一个方法
export const withLoading = (fn) => {
// 创建一个新方法
const newFn = (...args) => {
// 当新方法被执行时,执行老方法,并返回它的返回
// 等于新方法完全继承了老方法的入参、能力、返回值
return fn(...args)
}
// 返回这个新函数
return newFn
}
这就是一个最基础的函数式编程实例
。
OK,到这里我们已经确定了解决代码过长
的解决方案。
那就看一看本步的后续实现代码:
// 在不考虑`错误处理`和`传入Options`的情况下,下面代码已经实现了我们上面的设计
export const withLoading = (fn) => {
let loading;
const showLoading = () => {
loading = ElLoading.service()
}
const hideLoading = () => {
if (loading) {
loading.close()
}
}
const newFn = (...args) => {
showLoading()
const result = fn(...args)
hideLoading()
}
return newFn
}
3.2 封装痛点二:解决默认传参和自定义传参的问题
这个点就很容易了:
// 定义一个默认配置
const defaultOptions = {
lock: true,
text: '正在加载',
background: 'rgba(0, 0, 0, 0.1)'
}
// 增加第二个传参,可传入自定义配置
export const withLoading = (fn, options = {}) => {
...
const showLoading = (options) => {
loading = ElLoading.service(options)
}
// 进行assign
const _options = Object.assign(defaultOptions, options)
const newFn = (...args) => {
showLoading(_options)
}
return newFn
}
3.3 封装痛点三: 异常处理,封装try catch
先考虑一个最简单的场景,那就是,当传入的fn为同步方法
时,代码可以很简单,我们所需要做的,也仅仅是以下两点:
- 保证在catch到Error后,关闭loading
- 继续把catch到的Error扔出去
export const withLoading = (fn, options = {}) => {
const newFn = (...args) => {
try {
let result
showLoading(_options)
result = fn(...args)
hideLoading()
return result
} catch (err) {
hideLoading()
throw err
}
}
}
但这样很显然是不够的。
因为传入的方法可能是一个异步方法
,这种情况下我们会直接返回一个Promise对象
,并且立刻关闭loading
。
因此我们需要判断传入的方法是否是异步方法
,判断方法如下:
const result = fn(...args)
// 判断返回的是否是Promise
const isPromise = result instanceof Promise
如果不是Promise
,那就按上面同步逻辑
进行return即可。
如果是Promise
,那我们则需要进行catch,并进行错误处理
。
return result
.then((res) => {
hideLoading()
return res
})
.catch((err) => {
hideLoading()
throw err
})
四、收工大吉,看看效果
写一个简单的Demo分别测试下正常
和异常
的表现:
<template>
<div>
<el-button @click="queryBirds">获取人员</el-button>
<el-button @click="queryCars">获取车辆</el-button>
</div>
</template>
<script setup>
import { getBirds, getCars } from './mock';
import { withLoading } from './hooks/loading';
import { ElMessage } from 'element-plus';
const queryBirds = async () => {
// 你看,一行代码都不增加
const birds = await withLoading(getBirds)()
ElMessage.success(birds.map(t => t.name).join())
}
const queryCars = async () => {
try {
// 你看,一行代码都不增加
await withLoading(getCars)()
} catch(err) {
ElMessage.error(err.message)
}
}
</script>
完美!
五、源码奉上
转载自:https://juejin.cn/post/7035125917449977887