如何在 Vuex 中设置定时器?

How to set timer in Vuex?

我刚开始学习 Vue 和 Vuex。我正在尝试设置一个倒数计时器,当它被点击时从 5 秒开始计时。逻辑已放在 Vuex 存储中用于状态管理,但我无法将计时器设置为 运行(它停留在 5 不变)。

在 parent (Home.vue)

<template>
  <div class="home">
    <Header 
    @change-game-button="changeGameButton"
     :gameInActive="gameInActive"
     :gameActive="gameActive"
    />
  </div>
</template>

<script>
import Header from '../components/Header'

export default {
  name: 'Home',
  components: {
    Header,
  },
  data() {
    return {
      gameInActive: true,
      gameActive: false
    }
  },
  methods: {
    changeGameButton() {
      console.log("Game button fired");
      this.gameInActive = !this.gameInActive;
      this.gameActive = !this.gameActive;
    }
  }
}
</script>

在组件 (Header.vue)

<template>
  <div class="header-container">
    <StartResetButton 
    @btn-click="$emit('change-game-button')"
    :buttonText="gameInActive ? 'Start' : 'Restart'"
    :buttonColor="gameInActive ? '#90ee90' : '#FED8B1'"
    @click="gameInActive? 'resetCounter' : 'startCounter'"
    />
  </div>
  <div class="initialInstruction" v-if="gameInActive">{{ initialInstruction }}</div>
  <div class="gameStartTimer" v-if="gameActive">{{ gameStartTimer }}</div>
</template>

<script>
import StartResetButton from "./StartResetButton";
import { mapActions, mapMutations, mapState } from "vuex";

export default {
  components: {
    StartResetButton,
  },
  props: {
      gameInActive: Boolean,
      gameActive: Boolean,
  },
  computed: {
      ...mapState(["gameStartTimer", "initialInstruction"])
  },
  methods: {
    //   ...mapMutations(["countDown"]),
      ...mapActions(["startCounter", "resetCounter"])
  },
  emits: ['change-game-button']
};
</script>

在商店 (index.js)

import { createStore } from 'vuex'

export default createStore({
  state: {
    gameStartTimer: 5,
    initialInstruction: "Press Start to Begin"
  },
  mutations: {
    stopCounter(state) {
      state.gameStartTimer = 5
    },
    counter(state) {
      if (state.gameStartTimer > 0) {
        setTimeout(() => {
          state.gameStartTimer--;
        }, 1000)
      }
    }
  },
  actions: {
    startCounter(context) {
      context.commit('counter')
    },
    resetCounter(context) {
      context.commit('stopCounter')
    }
  },
  modules: {
  }
})

StartResetButton.vue

<template>
  <button 
  class="btn"
  @click="onClick()" 
  :style="{ background: buttonColor }">
    {{ buttonText }}
  </button>
</template>

<script>
export default {
  name: "StartResetButton",
  props: {
    buttonText: String,
    buttonColor: String,
  },
  methods: {
      onClick() {
          this.$emit('btn-click')
      }
  }
};
</script>

关于计时器为何不触发的任何提示?

首先点击StartResetButton.vue按钮,函数应该执行如下。

  1. @btn-click="$emit"('change-game-button') 应该先执行,这样 gameInActive 应该是 false。

  2. 必须执行 startCounter 操作。

使用 console.log 验证它是否按预期顺序运行。我看不到它是如何工作的,因为 post 没有 StartResetButton.vue 代码。

startCounter 动作然后执行计数器突变,如果函数正常执行,它将在 4 处停止,因为计数器突变只执行一次。

我建议通过仅将 gameStartTimer 减 1 并在 startCounter 操作中的 gameStartTimer 值大于 0 时使用超时每 1 秒执行一次计数器突变来修改计数器突变。

问题是这不会执行点击处理程序:

@click="gameInActive? 'resetCounter' : 'startCounter'"

v-on (@) 值应该是用作事件处理程序的函数,或者是根据事件计算的表达式。此表达式不会导致 resetCounter 等函数调用,因为 typeof (this.gameInActive? 'resetCounter' : 'startCounter') === 'string'。最好提供一个单一的处理程序来做出决定,而不是将条件放入模板中。

Vuex 支持 promises,异步操作最好使用 promise 控制流。 setTimeout回调执行一次。如果它应该递减多次,setTimeout 应该与 await 循环使用,或者应该使用 setInterval 代替。

突变是同步的并且通常是直接访问状态的细粒度函数。可以有decrementCountstartCounter

  state: {
    count: 5,
    counter: false
  },
  actions: {
    async startCounter({ state }) {
      state.counter = true;
      while (state.count > 0 && state.counter) {
        await new Promise(resolve => setTimeout(resolve, 1000));
        if (state.counter)
          state.count--;
      }
      state.counter = false;
    },
    resetCounter(context) {
      state.count = 5;
      state.counter = false;
    }
  },