You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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: {}
}

错误表现

  1. 初始运行报错,疑似出现在代码行:context.state.started = setInterval(context.dispatch('clockRunning'), 10);
  2. 修改为setInterval(() => context.dispatch('clockRunning'), 10)后,出现每秒大量报错、CPU占用率飙升至100%的问题
错误原因分析
  • 初始错误:context.dispatch('clockRunning')会立即执行该action,将返回的Promise对象传给setInterval,不会按10ms间隔执行逻辑
  • CPU飙升&报错的核心原因:
    1. 违反Vuex核心规范:所有state的修改必须通过mutation提交,禁止直接在action中修改context.state,会导致状态追踪异常
    2. 工具函数误用:zeroPrefix是纯同步工具函数,不需要定义为action,context.dispatch('zeroPrefix')返回Promise,把Promise作为函数调用会直接抛出异常,10ms执行一次的定时器会持续抛错,导致CPU占满
    3. 定时器为非序列化对象,存储在state中会导致Vuex状态持久化等场景出现异常,不推荐存到state
修复后的完整代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.06 18:06:01