Vue.js如何使用Vuex getters优化轮询逻辑避免应用卡顿
问题结论
- 用Vuex getters映射为组件计算属性是完全合适的方案,响应式的状态更新天然能解决你现在状态判断滞后的问题。
- 把同步状态管理、轮询启停的逻辑全迁移到Vuex store中是更优的实践,能彻底移除组件里零散的状态修改、定时器管理代码,后续维护成本低很多。
现有代码延迟15秒的根因
你现在的逻辑是:mounted时先等第一次请求完成,才启动15秒间隔的定时器,定时器第一次触发必须等够15秒,且只有等接口响应返回后才会执行状态判断,等于同步完成的信号最早也要等下一次轮询请求发出去、拿到结果才能触发,自然会有最长15秒的延迟。另外你把状态分别存在组件本地data和Vuex state两处,很容易出现状态不同步的问题,而且组件销毁时没有清理定时器,还会造成内存泄漏。
具体改造步骤
1. 改造Vuex store(index.js)
把状态常量、定时器管理、接口请求、状态修改的逻辑全收拢到store中,新增对应的getters、mutations、actions:
import Vue from 'vue'; import Vuex from 'vuex'; import _get from 'lodash/get'; import { clearPersistedTokens } from '../auth_utils'; // 引入同步状态查询接口 import { fetchImportsProductsSyncStatusRequest } from '../api/imports'; Vue.use(Vuex); // 同步状态枚举 const STATUS_INITIAL = 0, STATUS_IN_PROGRESS = 1, STATUS_SUCCESS = 2, STATUS_SYNCED = 3; const storeSettings = { state: { syncingProductsInProgress: null, productSyncCurrentStatus: STATUS_INITIAL, // 全局存储定时器ID,统一控制启停 syncStatusPollTimer: null }, getters: { // 对外暴露是否正在同步的状态 isProductSyncing: state => state.syncingProductsInProgress, // 对外暴露当前同步状态码 productSyncStatus: state => state.productSyncCurrentStatus }, mutations: { SET_SYNC_IN_PROGRESS(state, val) { state.syncingProductsInProgress = val; }, SET_SYNC_CURRENT_STATUS(state, status) { state.productSyncCurrentStatus = status; }, SET_SYNC_POLL_TIMER(state, timer) { state.syncStatusPollTimer = timer; }, CLEAR_SYNC_POLL_TIMER(state) { if (state.syncStatusPollTimer) { clearInterval(state.syncStatusPollTimer); state.syncStatusPollTimer = null; } } }, actions: { // 单次查询同步状态 async fetchProductSyncStatus({ commit, dispatch }) { commit('SET_SYNC_CURRENT_STATUS', STATUS_IN_PROGRESS); try { // 注意:需要把原请求方法改造成不依赖组件this的纯函数,只负责发请求返回结果 const res = await fetchImportsProductsSyncStatusRequest(); // 这里根据实际接口返回结构取同步中标识,按自己的接口改字段 const isSyncing = _get(res, 'data.inProgress', false); commit('SET_SYNC_IN_PROGRESS', isSyncing); if (!isSyncing) { commit('CLEAR_SYNC_POLL_TIMER'); commit('SET_SYNC_CURRENT_STATUS', STATUS_SYNCED); } return res; } catch (err) { console.error('查询产品同步状态失败', err); throw err; } }, // 启动轮询 startProductSyncPoll({ commit, dispatch }) { // 先清理已有定时器,避免重复开启轮询 commit('CLEAR_SYNC_POLL_TIMER'); // 启动后立刻发一次请求,不用等15秒,直接解决延迟问题 dispatch('fetchProductSyncStatus'); // 开启定时轮询 const timer = setInterval(() => { dispatch('fetchProductSyncStatus'); }, 15000); commit('SET_SYNC_POLL_TIMER', timer); }, // 停止轮询 stopProductSyncPoll({ commit }) { commit('CLEAR_SYNC_POLL_TIMER'); } } }; export default new Vuex.Store(storeSettings);
2. 改造fetch_all.vue组件
移除组件内零散的状态、定时器、状态修改方法,只保留触发轮询和销毁时清理的逻辑,通过mapGetters映射store中的状态即可:
<script> import { mapGetters } from 'vuex'; // 模板渲染如果用到状态常量就保留,不用可以删掉 const STATUS_INITIAL = 0, STATUS_IN_PROGRESS = 1, STATUS_SUCCESS = 2, STATUS_SYNCED = 3; export default { name: 'BackboneFetchAll', computed: { // 映射Vuex getters为组件计算属性,状态变更会自动响应式更新 ...mapGetters([ 'isProductSyncing', 'productSyncStatus' ]) }, mounted() { // 直接触发store里的启动轮询逻辑即可 this.$store.dispatch('startProductSyncPoll'); }, beforeDestroy() { // 组件销毁时必须停掉轮询,避免内存泄漏 this.$store.dispatch('stopProductSyncPoll'); } } </script>
改造收益
- 解决15秒延迟问题:启动轮询时立刻发起一次状态查询,同步完成的当下接口返回就会立刻更新状态、停掉轮询,不需要等下一个轮询周期。
- 状态统一管理:所有和产品同步相关的状态、逻辑全在Vuex中,不会再出现组件本地状态和全局状态不一致的问题,多个组件用到这个状态时不需要重复写逻辑。
- 可维护性更高:后续要改轮询间隔、状态判断规则、接口字段,只需要修改store里的代码,不需要逐个业务组件调整。
注意:原来的
fetchImportsProductsSyncStatusRequest方法如果内部依赖传入的组件this做状态修改、提示弹窗之类的操作,需要先解耦,把这类逻辑移到Vuex action或者全局拦截器里,让接口方法只负责纯粹的请求发送、结果返回。
内容的提问来源于stack exchange,提问作者mr_muscle
相关产品推荐
相关产品推荐

