VueJS не выполняет повторную визуализацию до завершения цикла

#javascript #vue.js #vuejs2

#javascript #vue.js #vuejs2

Вопрос:

У меня есть этот фрагмент кода VueJS:

 new Vue({
  el: '#app',
  data: {
    tiles: [
      { isActive: false },
      { isActive: false },
      { isActive: false },
      { isActive: false },
      { isActive: false }
    ]

  },
  methods: {
    startWithLoop: function() {
      console.log("startWithLoop");
      for(var i = 0; i < 10000; i   ) { this.blink() };
    },
    startWithInterval: function() {
      console.log("startWithInteral");
      setInterval(this.blink);
    },
    blink: function(){
      console.log("blink");
      var index = Math.floor(Math.random() * this.tiles.length);
      this.tiles[index].isActive = !this.tiles[index].isActive;
    }
  }
})
  

Если я вызываю метод startWithInterval , я могу видеть в представлении, как tiles постоянно меняется состояние.

Если я вызываю метод startWithLoop , я не вижу никаких изменений в представлении, пока цикл не будет завершен.

Вот JSFiddle

Как я могу инициировать изменения в представлении на каждом шаге цикла?

Комментарии:

1. Это связано с тем, что ваш цикл for поддерживает занятость основного потока: в этом случае у браузера нет времени между циклами для обновления DOM, поэтому он откладывает обновление до завершения цикла for.

2. Его задача со стеком, setInterval помещает функцию для вызова в стек, в то время как цикл for не делает его не асинхронным, он просто зависает до следующего нажатия

3. Вы могли бы использовать $forceUpdate()

Ответ №1:

Нет, именно так Javascript eventloop работает в браузерах (и не только).

Вы можете себе представить, что Javascript выполняется только в «промежутках между моментами», так что ваша картина того, что происходит в браузере, является моментальным снимком момента.

Ответ №2:

Вы могли бы написать что-то похожее на цикл, которое setTimeout позволяет Vue замечать изменения, а затем отправлять их в DOM.

 beforeDestroy() {
  if (this.timeout != null) {
    clearTimeout(this.timeout);
    this.timeout = null;
  }
},
startWithLoop: function() {
  console.log("startWithLoop");
  let i = 0
  const iter = () => {
    this.blink();
    i  = 1;
    if (i < 10000) {
      this.timeout = setTimeout(iter);
    }
  }
  iter();
},
  

Рабочая скрипка с изменениями, указанными выше: https://jsfiddle.net/ssorallen/9pqscat1/3 /

Комментарии:

1. Зачем так сильно усложнять цикл? разве этого недостаточно for(var i = 0; i < 10000; i ) { setTimeout(this.blink) }; ?

2. Конечно, вы можете удалить это, если хотите. Я добавил это, потому что это было в вашем исходном коде.

3. Я хотел предложить упрощенную версию вашего кода, я был вынужден отредактировать ваш ответ, но подумал, что вам это может не понравиться. Я создал новый ответ, принимая ваше предложение, но упростив код, чтобы перейти непосредственно к делу. Спасибо

Ответ №3:

Подводя итог и объединяя все предложения, я понял, что:

  • В середине цикла JS VueJS ничего не будет повторно отображать

Поэтому вам нужно переместить итерации вашего цикла в другой процесс. Я думал, что promises может быть решением, но проще использовать setTimeout() без параметра delay .

Поэтому вместо этого:

 for(var i = 0; i < 10000; i   ) { 
  this.blink() 
};
  

Мой код будет выглядеть так:

 for(var i = 0; i < 10000; i   ) { 
  setTimeout(this.blink)
}
  

Комментарии:

1. Это будет редко, но этот код может получить доступ к компоненту после его уничтожения. Если компонент setTimeout находится в очереди и уничтожен до истечения времени ожидания, this.blink будет вызван для уничтоженного компонента. Скорее всего, это не вызовет проблем, но именно поэтому мой код включает в себя beforeDestroy код и, чтобы сохранить ссылку на время ожидания в очереди.