在我们业务中,有很多需要通过轮询去刷新列表状态的场景。在轮询(短轮询)的当前浏览器tab页失活时,列表仍在刷新(发送请求)。这样会给服务器造成很大的压力、浪费带宽和服务器资源,同时前端监控到的某些数据也会失真。
所以在基于短轮询的基础上,通过监听 document 元素的可见性来开启/停止轮询。
失活:切到浏览器的其他tab页、最小化,浏览器窗口被其他app或浏览器窗口完全覆盖。
优化前–轮询
// component.vue
export default {
data() {
return {
timer: null
}
},
methods: {
startTimer() {
this.closeTimer()
this.timer = setInterval(() => {
this.getTableDate()
}, 15000)
},
closeTimer() {
clearInterval(this.timer)
}
}
}
实现高亮轮询,隐藏时关闭轮询
- App.vue 监听浏览器高亮状态
// app.vue
export default {
mounted() {
this.init()
},
methods: {
// 监听列表高亮,
window.addEventListener('visibilitychange', () => {
this.$store.dispatch('commitDomVisible', document.visibilityState === 'visible')
}, false)
}
}
- vuex 缓存是否高亮的状态
const common = {
state: {
domIsVisible: true
},
mutations: {
setVisible(state, isVisible) {
state.domIsVisible = isVisible
}
}
}
- 封装了一个mixin,方便多组件调用
// listPolling.js
export default {
data() {
return {
timer: null,
isActive: false // 判断组件是否失活,避免在失活时开启组件的轮询
}
},
watch: { // 浏览器窗口可见时开启轮询
'$store.state.common.domIsVisible': function(val) {
if (val) {
this.init()
} else {
this.closeTimer()
}
}
},
activated() {
this.isActive = true
},
// 直接切换菜单栏时执行
deactivated() {
this.isActive = false
this.closeTimer()
},
// keep-alive 情况下,路由返回时不会执行 deactivated
destroyed() {
this.closeTimer()
},
methods: {
init() {
if (this.isActive) {
this.getTableData()
this.startTimer()
}
},
startTimer() {
this.closeTimer()
this.timer = setInterval(() => {
this.getTableData()
}, 15000)
},
closeTimer() {
clearInterval(this.timer)
}
}
}
- 需要轮询的页面调用:
import listPolling from '@/mixins/listPolling'
export default {
mixins: [listPolling],
data() {
return{
...
}
},
...
}
**混入:**当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项
- 选项合并:数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。
- 同名钩子函数将合并为一个数组,因此都将被调用。
- 值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。
根据vue 中mixin的特性,我们在某些特殊的轮询组件里,可以自定义轮询时常,开始、结束轮询的时机等。
vue3
// listPolling.js
import {
ref, watch, onActivated, onDeactivated, onUnmounted, watchEffect
} from 'vue'
import { useWinStore } from '@/stores'
export function useListPolling(getList, pollingInterval = 15000) {
const timer = ref(null)
const isActive = ref(false)
const store = useWinStore()
const init = () => {
if (isActive.value) {
getList()
startTimer()
}
}
const clearTimer = () => {
if (timer.value) {
clearInterval(timer.value)
timer.value = null
}
}
const startTimer = () => {
clearTimer()
timer.value = setInterval(() => {
getList()
}, pollingInterval)
}
watch(() => store.visibilityState, (val) => {
console.log('p--watch', val)
if (val) {
init()
} else {
clearTimer()
}
})
onActivated(() => {
console.log('p--onActivated')
isActive.value = true
init()
})
onDeactivated(() => {
console.log('p--onDeactivated')
isActive.value = false
clearTimer()
})
return {
init,
clearTimer,
startTimer,
isActive,
timer
}
}
// components.vue
<script setup>
import { useListPolling } from './listPolling'
const { timer, isActive } = useListPolling(getTableData)
</script>