【问题标题】:Vue data updates in method with setInterval() but the dom/view doesn't updateVue 数据在 setInterval() 方法中更新,但 dom/view 不更新
【发布时间】:2020-04-28 13:44:07
【问题描述】:

在我的方法中,我有一个在视图中使用@click 触发的函数。 此函数启动一个计时器,计时器的工作原理与我在 DevTools 中看到的一样,但它仅在我点击刷新时在 DevTools 中更新。

另外,在视图中,计时器根本不显示,因为它只呈现一次并且它不寻找更新。

更新: 应要求提供完整代码

<template>
<div class="container-fluid">
    <div class="card mt-2 p-3 w-75 mx-auto">
        <h2>League Summoners Timers</h2>
        <hr>
        <div>
            <h5>Enemy team</h5>
            <div class="row">
                <div class="col-md-12 col-lg-2" v-for="index in lanes" :key="index">
                    <div class="card">
                        <div class="card-header">
                            <p class="m-0">{{ index }}</p>
                        </div>
                        <div class="card-body">
                            <div v-show="!gameStarted">
                                <div v-show="selected[index.toLowerCase()].length < 2" class="spell-image-row" v-for="spell in spells" :key="spell.name" @click="onClickSelection(spell, index)">
                                    <img class="spell-image m-1" :alt="spell.name" :src="spell.image">
                                </div>
                                <div v-show="selected[index.toLowerCase()].length >= 2">
                                    <span class="alert alert-primary">Spells selected</span>
                                    <ul class="mt-3">
                                        <li v-for="selectedSpell in selected[index.toLowerCase()]" :key="selectedSpell">
                                            {{ selectedSpell }}
                                        </li>
                                    </ul>
                                </div>
                            </div>
                            <div v-show="gameStarted">
                                <div v-for="spell in selected[index.toLowerCase()]" :key="index+spell">
                                    <div class="row">
                                        <img style="float: left" class="my-1" :src="spells[spell.toLowerCase()].image" :alt="spell.name" @click="onClickStartTimer(spell, index)">
                                        <p>{{ timers[index.toLowerCase()][spell.toLowerCase()] }}</p>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <div class="mt-3">
            <button class="btn btn-primary mr-3" @click="gameStarted = true" v-show="checkSelected() && !gameStarted">Start game</button>
            <button class="btn btn-danger" onClick="window.location.reload();">Reset</button>
        </div>
    </div>
</div>

<script>
    export default {
        name: "LeagueTimers",
        data() {
            return {
                gameStarted: false,
                lanes: ['Top', 'Jungle', 'Mid', 'ADC', 'Support'],
                spells: {
                    teleport: {
                        name: 'Teleport',
                        image: require('../assets/images/Teleport.png'),
                        cooldown: 420,
                    },
                    ignite: {
                        name: 'Ignite',
                        image: require('../assets/images/Ignite.png'),
                        cooldown: 180,
                    },
                    heal: {
                        name: 'Heal',
                        image: require('../assets/images/Heal.png'),
                        cooldown: 240,
                    },
                    ghost: {
                        name: 'Ghost',
                        image: require('../assets/images/Ghost.png'),
                        cooldown: 300,
                    },
                    flash: {
                        name: 'Flash',
                        image: require('../assets/images/Flash.png'),
                        cooldown: 300,
                    },
                    exhaust: {
                        name: 'Exhaust',
                        image: require('../assets/images/Exhaust.png'),
                        cooldown: 210,
                    },
                    cleanse: {
                        name: 'Cleanse',
                        image: require('../assets/images/Cleanse.png'),
                        cooldown: 210,
                    },
                    barrier: {
                        name: 'Barrier',
                        image: require('../assets/images/Barrier.png'),
                        cooldown: 180,
                    },
                    smite: {
                        name: 'Smite',
                        image: require('../assets/images/Smite.png'),
                        cooldown: 15,
                    },
                },
                selected: {
                    top: [],
                    jungle: [],
                    mid: [],
                    adc: [],
                    support: [],
                },
                timers: {
                    top: {},
                    jungle: {},
                    mid: {},
                    adc: {},
                    support: {},
                },
            }
        },
        methods: {
            onClickSelection(spell, lane) {
                this.selected[lane.toLowerCase()].push(spell.name);

            },
            onClickStartTimer(spell, lane) {
                if (!this.timers[lane.toLowerCase()][spell.toLowerCase()]) {
                    this.startTimer(spell.toLowerCase(), lane.toLowerCase(), this.spells[spell.toLowerCase()].cooldown);
                } else {
                    console.log('runt al');
                }
            },
            checkSelected() {
                for (let [key] of Object.entries(this.selected)) {
                    if (this.selected[key].length !== 2) {
                        return false;
                    }
                }
                return true;
            },
            startTimer(spell, lane, cooldown) {
                this.timers[lane][spell] = cooldown;
                let timers = this.timers;
                setInterval(function (){
                    timers[lane][spell]--;
                    // console.log(lane+spell + " - " + timers[lane][spell]);
                    // console.log(typeof timers[lane][spell])
                    this.$forceUpdate();
                }, 1000);
            },
        },
    }
</script>

到目前为止,我尝试使用的是 watch: {} 和 computed: {},但到目前为止没有任何效果。我必须补充一点,我对 Vue 很陌生。

【问题讨论】:

  • 请发布整个代码。

标签: javascript vue.js setinterval vue-reactivity


【解决方案1】:

尝试以这种方式声明您的 setInterval 函数

 setInterval(() => {
    this.timers[lane][spell]--;
    console.log(lane+spell + " - " + timers[lane][spell]);
    console.log(typeof timers[lane][spell])
    // this.$forceUpdate();
  }, 1000);

使用箭头函数,您可以使用 this.timers 访问属性数据。 使用 Function 语法会创建一个新的作用域,并且 this.timers 不可用

如果要使用函数语法,可以通过这种方式绑定this对象

  setInterval(function () {
    this.timers[lane][spell]--;
    console.log(lane+spell + " - " + timers[lane][spell]);
    console.log(typeof timers[lane][spell])
  }.bind(this), 1000)

此外,要在数据属性计时器中添加响应性,您必须首先声明它们:

timers:
  { top:
    { teleport: 0,
      smite: 0, 
      ... },
    ...
   }

如果你想动态添加这些属性,vue set 属性是这样做的,你可以在这里阅读更多关于它https://vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats

【讨论】:

  • 您好,感谢您的回答!我尝试使用箭头函数并应用了您的代码。计时器仍然可以正常工作,如果我每秒刷新一次,它会显示在日志和 DevTools 中。但不幸的是仍然没有在页面上更新。 :(
  • 我想通了,问题是 timers 对象没有 timers.top.smite 作为初始属性,所以在 startTimer 函数中添加属性不会使 timers.top.smite 反应,并且两种方式绑定不可用。将计时器声明为: timers: { top: { teleport: 0, smite: 0, ... }, ... } 就可以了。您也可以在此处阅读有关 Vue 设置属性的信息 vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats,这样您就可以动态添加属性。 – Andres Foronda 7 分钟前 删除
  • 谢谢!我声明了变量,现在它可以工作了。也许我会让它与 Vue 集一起工作,但现在这工作正常! :)
  • 很高兴知道!玩得开心,让我稍后玩:D
猜你喜欢
  • 1970-01-01
  • 2019-08-04
  • 2021-11-06
  • 1970-01-01
  • 2021-12-11
  • 2020-11-12
  • 2021-04-01
  • 1970-01-01
  • 2020-12-17
相关资源
最近更新 更多