【问题标题】:How to set timer in Vuex?如何在 Vuex 中设置计时器?
【发布时间】:2021-09-22 05:24:45
【问题描述】:

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

在父级 (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>

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

【问题讨论】:

  • 因为这个片状的表达? @click="gameInActive? 'resetCounter' : 'startCounter'" 。它评估为“resetCounter”字符串,而不是方法。不要在模板中写出比你应该写的更多的逻辑。突变也应该是同步的,你正试图在那里做动作的工作。
  • 谢谢。我现在通过将大部分逻辑放到操作中来简化代码。我设法让 setTimeout 倒计时 1,但它只减少了 1 秒并停在那里。我正在弄清楚如何循环播放它,直到 5 秒结束。

标签: javascript html vue.js vuex


【解决方案1】:

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

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

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

Vuex 支持 Promise,最好将 Promise 控制流用于异步操作。 setTimeout 回调执行一次。如果应该多次递减,则应在循环中使用setTimeoutawait,或者应使用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;
    }
  },

【讨论】:

  • 谢谢,这很好用。只是关于 setTimeout 和 setInterval 的问题,是否最好使用 setTimeout?我正在研究两者之间的区别,有人提到内存出血。并不是说它对 5 秒计数器有影响,但我更感兴趣的是编写这些代码的良好做法。
  • 如果操作正确,它们都不应该在现代浏览器中导致内存泄漏。不同之处在于,错误将在较长的时间间隔内与 setTimeout 相加,但会导致代码更细化。 I在这种情况下,带有 setTimeout 的 startCounter().then(...) 将在计数器中止时在 1 秒内解决。使用 setInterval 可能会更快,但需要更复杂的中止机制才能通过 Promise 控制它。
【解决方案2】:

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

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

  2. 必须执行 startCounter 操作。

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

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

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

【讨论】:

  • 谢谢。我添加了 StartResetButton.vue 供您参考。我现在正在根据您的建议浏览文件,看看可以做什么。
猜你喜欢
  • 2018-06-16
  • 2010-10-02
  • 2011-06-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多