Виртуальный DOM Vue не обновляется немедленно, когда за ним следует дорогостоящее вычисление

#javascript #asynchronous #vue.js

#javascript #асинхронный #vue.js

Вопрос:

Я пытаюсь добавить экран загрузки, который отображается, пока мое приложение выполняет дорогостоящее хэширование и расшифровку некоторых данных (занимает 2-3 секунды). Когда я экспериментально удаляю дорогостоящие части, следующие за тем, что должно обновлять DOM, экран загружается немедленно. В противном случае он не будет отображаться до завершения дорогостоящей функции. Есть идеи, что я напортачил?

 export default {
  data(){
    return {
      loading: false,
      password: ''
    }
  },
  methods: {
    async unlock(){
      this.loading = true;
      await this.$nextTick()
      // DOM should update, but it doesn't
      try{
        await this.$root.UnlockVaultPassword(this.password);
      } catch(err){
        this.loading = false;
        return;
      }
      // DOM updates once finished with hashing and ciphering
      this.loading = false;
      this.$router.push({name: 'vault'});
    }
  }
}  

Я думаю, что, поскольку vue обновляет dom асинхронно, я испытываю состояние гонки, когда он не может поместиться в обновление dom во время хэширования. Как я могу ДОЖДАТЬСЯ завершения dom?

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

1. Одна только настройка this.loading = true запускает повторный запуск?

2. правильно. Это определенно как-то связано с вычислительной дороговизной.

3. Как насчет того, чтобы попробовать это.$forceUpdate(), чтобы заставить vue обновить DOM.

4. Предостережение с $forceUpdate : он не будет повторно отображать все дочерние элементы.

5. Да, я пробовал это, не работает. Рендеринг запускается, но по какой-то причине хэширование мешает

Ответ №1:

Может быть, попробовать использовать mounted перехват? И вызывайте unlock функцию как отдельную операцию после $nextTick для хорошего разделения проблем.

   mounted () {
    this.loading = true;

    this.$nextTick(function () {
      this.unlock();
    })
  }

  methods: {
    unlock: function() {
      // unlock code
      this.loading = false;
    }
  }
  

ОБНОВЛЕНИЕ: попробуйте использовать функцию-обработчик, которая сначала устанавливается loading при отправке формы, затем откладывается на unlock в качестве обратного вызова на $nextTick , вот так:

   methods: {
    handleFormSubmit: function() {
      this.loading = true;
      this.$nextTick(this.unlock);
    },
    unlock: function() {
      // unlock code
      this.loading = false;
    }
  }
  

ОБНОВЛЕНИЕ 2: если ничего из вышеперечисленного не работает, это начинает звучать как ошибка в ожидаемом поведении $nextTick , поскольку рендеринг не завершен до выполнения следующего кода. Что произойдет, если вы используете setTimeout для принудительной разблокировки процессов до конца стека выполнения?

   methods: {
    handleFormSubmit: function() {
      this.loading = true;
      this.$nextTick(() => {
        setTimeout(this.unlock, 0);
      });
    },
    unlock: function() {
      // unlock code
      this.loading = false;
    }
  }
  

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

1. Проблема с этим заключается в том, что функция «разблокировать» запускается при отправке формы

2. Ах. Но как насчет передачи unlock обратного вызова в $nextTick ? Позвольте мне изменить мой пример.

3. Я думаю, что, возможно, есть условие гонки, при котором $nextTick выполняется, но DOM не был полностью повторно отрисован ко времени UnlockVaultPassword вызова. Кажется странным, потому что ваш код выглядит правильно.

4. Другое предложение: выгрузка сложного хэширования / расшифровки в асинхронный процесс, либо на серверную часть, либо, возможно, на веб-работников . В любом случае может иметь преимущества в производительности, если ваш код расшифровки блокирует однопоточные процессы JS на 2-3 секунды.

5. К сожалению, тот же эффект, и я не могу выгрузить на серверную часть, потому что приложение не полагается на серверную часть (electron)

Ответ №2:

Итак, я нашел обходной путь… Мне это не нравится, но это не так уж ужасно. На самом деле я могу включить обратный вызов в цикл событий, так что мое хэширование произойдет только после завершения рендеринга.

 export default {
  data(){
    return {
      loading: false,
      password: ''
    }
  },
  methods: {
    async unlock(){
      this.loading = true;
      setTimeout(() => {
        try{
          await this.$root.UnlockVaultPassword(this.password);
        } catch(err){
          this.loading = false;
          return;
        }
        this.loading = false;
        this.$router.push({name: 'vault'});
      }, 0)
    }
  }
}