场景描述:
在做数据大屏项目过程中,一个页面中可能有许多图表组件,每个图表组件需要间隔不同的时间去刷新数据。
如果每个组件中都写一个定时器去刷新数据,总感觉不是很优雅,会写很多重复的代码,于是我打算用hooks去抽离出这部分重复的代码。
思路:
1. 全局只使用一个定时器,每隔1s向外通知一个事件,在需要定时刷新数据的组件中监听到事件后自增一个秒数,用 (自增秒 % 需要几秒更新 === 0)就可以判断是否到达一次间隔的时间,从而执行刷新数据的方法,然后就是需要将上述逻辑抽离到hooks函数中。
第一步:定义全局事件mitt
// @/utils/mitter.js
// mitt事件总线
import mitt from 'mitt'
const mitter = mitt()
export default mitter
第二步:定义全局定时器
// @/utils/timer.js
import mitter from '@/utils/mitter'
// 全局定时器,每1秒向外面广播时间更新事件
const timer = setInterval(() => {
mitter.emit('time_update')
}, 1000)
export default timer
使用定时器 (建议在App.vue中使用)
// App.vue 中引入timer
import timer from '@/utils/timer'
import { onBeforeUnmount } from 'vue'
export default {
setup() {
onBeforeUnmount(() => timer && clearInterval(timer))
}
}
第三步:定义 useTimeExecutor.js 定时执行器hooks
// @/hooks/useTimeExecutor.js
import { ref, onMounted, onBeforeUnmount, onActivated, onDeactivated } from 'vue'
import mitter from '@/utils/mitter'
/**
* 统一处理定时函数的hooks
* @param options: Object
* options.second: Number 请求的间隔秒
* options.func: Function 需要执行的函数
* options.immediate: Boolean 是否立即执行
* options.wait: Boolean 是否等待执行(如果上一个loading未执行完,就不会执行)
* options.disabled: Boolean 是否禁用(如果希望手动执行exec就开启此选项)
*/
export default function (options = {}) {
const {
second = 5,
immediate = true,
wait = true,
disabled = false,
func = function () {}
} = options
let current = 0
let isListening = false
const isPause = ref(false)
const isLoading = ref(false)
// 页面挂载时执行一次,且开启监听
onMounted(() => {
immediate && exec()
on()
})
// 使用keep-alive页面激活时开启监听
onActivated(() => on())
// 开启监听
const on = () => {
if (!disabled && !isListening) {
isListening = true
mitter.on('time_update', onTime)
}
}
// 关闭监听
const off = () => {
mitter.off('time_update', onTime)
isListening = false
}
// 监听到时间改变, 当满足时间间隔时执行函数
const onTime = () => {
if (isPause.value) return
current++
if (current % second === 0) exec()
}
// 暂停执行
const pause = () => (isPause.value = true)
// 继续执行
const play = () => (isPause.value = false)
// 执行函数
const exec = async () => {
if (wait && isLoading.value) return
isLoading.value = true
let res
try {
res = await func()
} catch (e) {
throw new Error(e) // 可统一处理错误
} finally {
isLoading.value = false
}
return res
}
// 页面卸载时销毁监听
onBeforeUnmount(() => off())
// 使用keep-alive页面隐藏时销毁监听
onDeactivated(() => off())
return {
play,
pause,
exec,
isLoading
}
}
在需要定时刷新数据的组件中使用:
import { defineComponent, ref } from 'vue'
import useTimeExecutor from '@/hooks/useTimeExecutor'
export default defineComponent({
setup() {
const data = ref({})
// 更新数据的方法
const fetchData = async () => {
const res = await http.get('/api/some/data')
data.value = res
}
// 使用hooks, 表示每隔10秒会执行fetchData去更新数据
const { pause, play, isLoading } = useTimeExecutor({
second: 10,
func: fetchData
})
// 可自行暂停/继续更新数据
const handleButtonClick = e => {
e ? pause() : play()
}
return {
data,
isLoading ,
handleButtonClick
}
}
})
总结
代码抽离复用后,我们的组件中减少了许多样板代码,只需要使用useTimeExecutor就能让组件中的函数拥有定时请求的功能,并且可以随时暂停/继续更新,不用频繁地写定时器和清除定时器了,一切的逻辑都在useTimeExecutor函数钩子中处理了,这也是组合式API + Hooks对代码复用逻辑抽离的优点的体现。
如果代码发现可优化欢迎指正😁。