Vue.js用Vuex重构秒表报TypeError: context.dispatch(...) is not a function如何解决
问题复现
原有正常运行的组件内秒表代码
Stopwatch.vue
<script> export default { data() { return { time: "00:00.000", timeBegan: null, timeStopped: null, stoppedDuration: 0, started: null, running: false, }; }, methods: { start() { if (this.running) return; if (this.timeBegan === null) { this.timeBegan = new Date(); } if (this.timeStopped !== null) { this.stoppedDuration += new Date() - this.timeStopped; } this.started = setInterval(this.clockRunning, 10); this.running = true; }, clockRunning() { let currentTime = new Date(); let timeElapsed = new Date( currentTime - this.timeBegan - this.stoppedDuration ); let min = timeElapsed.getUTCMinutes(); let sec = timeElapsed.getUTCSeconds(); let ms = timeElapsed.getUTCMilliseconds(); this.time = this.zeroPrefix(min, 2) + ":" + this.zeroPrefix(sec, 2) + "." + this.zeroPrefix(ms, 3); }, zeroPrefix(num, digit) { let zero = ""; for (let i = 0; i < digit; i++) { zero += "0"; } return (zero + num).slice(-digit); }, }, mounted() { this.start(); } }; </script>
迁移到Vuex后的错误代码
store/stopwatch/index.js
export default { state: { time: "00:00.000", timeBegan: null, timeStopped: null, stoppedDuration: 0, started: null, running: false, }, actions: { start(context) { if (context.state.running) return; if (context.state.timeBegan === null) { context.state.timeBegan = new Date(); } if (context.state.timeStopped !== null) { context.state.stoppedDuration += new Date() - context.state.timeStopped; } context.state.started = setInterval(context.dispatch('clockRunning'), 10); context.state.running = true; }, clockRunning(context) { let currentTime = new Date(); let timeElapsed = new Date( currentTime - context.state.timeBegan - context.state.stoppedDuration ); let min = timeElapsed.getUTCMinutes(); let sec = timeElapsed.getUTCSeconds(); let ms = timeElapsed.getUTCMilliseconds(); context.state.time = context.dispatch('zeroPrefix')(min, 2) + ":" + context.dispatch('zeroPrefix')(sec, 2) + "." + context.dispatch('zeroPrefix')(ms, 3); }, zeroPrefix(num, digit) { let zero = ""; for (let i = 0; i < digit; i++) { zero += "0"; } return (zero + num).slice(-digit); } }, mutations: {}, getters: {} }
错误表现
- 初始运行报错,疑似出现在代码行:
context.state.started = setInterval(context.dispatch('clockRunning'), 10); - 修改为
setInterval(() => context.dispatch('clockRunning'), 10)后,出现每秒大量报错、CPU占用率飙升至100%的问题
错误原因分析
- 初始错误:
context.dispatch('clockRunning')会立即执行该action,将返回的Promise对象传给setInterval,不会按10ms间隔执行逻辑 - CPU飙升&报错的核心原因:
- 违反Vuex核心规范:所有state的修改必须通过mutation提交,禁止直接在action中修改
context.state,会导致状态追踪异常 - 工具函数误用:
zeroPrefix是纯同步工具函数,不需要定义为action,context.dispatch('zeroPrefix')返回Promise,把Promise作为函数调用会直接抛出异常,10ms执行一次的定时器会持续抛错,导致CPU占满 - 定时器为非序列化对象,存储在state中会导致Vuex状态持久化等场景出现异常,不推荐存到state
- 违反Vuex核心规范:所有state的修改必须通过mutation提交,禁止直接在action中修改
修复后的完整代码
store/stopwatch/index.js
// 抽离纯工具函数,不要放在Vuex的actions/mutations中 const zeroPrefix = (num, digit) => { let zero = ""; for (let i = 0; i < digit; i++) { zero += "0"; } return (zero + num).slice(-digit); }; // 定时器变量单独存储,不放入state let timer = null; export default { namespaced: true, // 开启命名空间,避免多模块冲突 state: { time: "00:00.000", timeBegan: null, timeStopped: null, stoppedDuration: 0, running: false, }, mutations: { SET_TIME_BEGAN(state, value) { state.timeBegan = value; }, SET_STOPPED_DURATION(state, value) { state.stoppedDuration = value; }, SET_RUNNING(state, value) { state.running = value; }, SET_TIME(state, value) { state.time = value; }, SET_TIME_STOPPED(state, value) { state.timeStopped = value; } }, actions: { start({ state, commit, dispatch }) { if (state.running) return; if (state.timeBegan === null) { commit('SET_TIME_BEGAN', new Date()); } if (state.timeStopped !== null) { commit('SET_STOPPED_DURATION', state.stoppedDuration + (new Date() - state.timeStopped)); } // 定时器正确写法,10ms触发一次时间更新 timer = setInterval(() => dispatch('clockRunning'), 10); commit('SET_RUNNING', true); }, clockRunning({ state, commit }) { const currentTime = new Date(); const timeElapsed = new Date( currentTime - state.timeBegan - state.stoppedDuration ); const min = timeElapsed.getUTCMinutes(); const sec = timeElapsed.getUTCSeconds(); const ms = timeElapsed.getUTCMilliseconds(); // 直接调用抽离的工具函数,不需要走dispatch const timeStr = `${zeroPrefix(min, 2)}:${zeroPrefix(sec, 2)}.${zeroPrefix(ms, 3)}`; commit('SET_TIME', timeStr); }, // 补充暂停、重置方法,完善功能 pause({ state, commit }) { if (!state.running) return; clearInterval(timer); commit('SET_TIME_STOPPED', new Date()); commit('SET_RUNNING', false); }, reset({ commit }) { clearInterval(timer); timer = null; commit('SET_TIME', '00:00.000'); commit('SET_TIME_BEGAN', null); commit('SET_TIME_STOPPED', null); commit('SET_STOPPED_DURATION', 0); commit('SET_RUNNING', false); } }, getters: { currentTime: state => state.time, isRunning: state => state.running } }
组件内调用示例
<script> import { mapGetters, mapActions } from 'vuex'; export default { computed: { ...mapGetters('stopwatch', ['currentTime', 'isRunning']) }, methods: { ...mapActions('stopwatch', ['start', 'pause', 'reset']) }, mounted() { this.start(); } } </script> <template> <div>{{ currentTime }}</div> <button @click="start" :disabled="isRunning">启动</button> <button @click="pause" :disabled="!isRunning">暂停</button> <button @click="reset">重置</button> </template>
内容的提问来源于stack exchange,提问作者Aras121
相关产品推荐
相关产品推荐

