Дросселирование или устранение отклонений асинхронных вызовов в Vue 2 при передаче аргументов в дебаунтированную функцию

У меня есть приложение Vue 2, которое использует массив объектов для поддержки виджета поиска / множественного выбора, предоставляемого vue-multiselect < / а>.

Я просмотрел руководство по миграции Vue 1 -> 2 на противодействие вызовам, но в приведенном ими примере не распространяются аргументы из элементов DOM в бизнес-логику.

Прямо сейчас select запускает события изменения при каждом нажатии клавиши, но я хотел бы задросселировать это (например, с lodash # throttle), поэтому я не нажимаю свой API каждые несколько миллисекунд, пока они печатают.

import {mapGetters} from 'vuex';
import { throttle } from 'lodash';

import Multiselect from 'vue-multiselect'

export default {
  components: {
    Multiselect
  },
  data() {
    return {
      selectedWork: {},
      works: [],
      isLoading: false
    }
  },
  computed: {
    ...mapGetters(['worksList']),
  },
  methods: {
    getWorksAsync: throttle((term) => {
      // the plan is to replace this with an API call
      this.works = this.worksList.filter(work => titleMatches(work, term));
    }, 200)
  }
}

Проблема: когда пользователь вводит текст в поле выбора, я получаю сообщение об ошибке:

TypeError: Cannot read property 'filter' of undefined

что происходит потому, что this.worksList undefined внутри функции throttle.

Любопытно, что когда я использую отладчик инструментов разработчика, this.worksList имеет значение, которое мне нужно разыменовать, а this относится к компоненту Vue.

В настоящее время я не вызываю API из компонента, но проблема остается прежней:

  1. Как я могу ограничить этот вызов и получить правильный this контекст для обновления моего this.works списка? РЕДАКТИРОВАТЬ: это объясняется в Vue Watch не запускается при использовании axios
  2. Мне также нужно захватить строку запроса пользователя из виджета с множественным выбором для перехода к вызову API.

Каков правильный шаблон в Vue 2?


person Matt Morgan    schedule 20.04.2018    source источник
comment
Возможный дубликат Vue Watch не срабатывает при использовании axios   -  person zero298    schedule 23.04.2018
comment
@ zero298 Я думаю, что проблема области видимости (стрелка fn vs function) такая же, как и в проблеме, на которую вы ссылались. Однако проблема получения значения строки запроса из пользовательского интерфейса, когда оно не привязано к модели, была дополнительной проблемой. Может быть, это две разные проблемы, но казалось, что это потенциально та ситуация, в которой могут оказаться другие.   -  person Matt Morgan    schedule 23.04.2018


Ответы (2)


Я столкнулся с той же проблемой при использовании lodash.debounce. Я большой поклонник синтаксиса стрелок, но я обнаружил, что это приводит к сбою _.throttle(), _.debounce() и т. Д.

Очевидно, мой код отличается от вашего, но я сделал следующее, и он работает:

export default {
   ...,
   methods: {
     onClick: _.debounce(function() {
       this.$emit('activate', this.item)
     }, 500)
  }
}

Несмотря на то, что я не использую здесь синтаксис стрелок, this по-прежнему ссылается на компонент внутри отклоненной функции.

В вашем коде это будет выглядеть так:

export default {
  ...,
  methods: {
    getWorksAsync: throttle(function(term) {
      // the plan is to replace this with an API call
      this.works = this.worksList.filter(work => titleMatches(work, term));
    }, 200)
  }
}

Надеюсь, это поможет!

person dzwillia    schedule 20.04.2018
comment
Спасибо @dzwillia. Я думаю, что важное различие между вашей ситуацией и моей заключается в том, что компонент <multiselect> привязан к модели selectedWork, которая не является значением, которое нам нужно для вызова внутреннего API. Нам действительно нужна строка поиска пользователя. Но регулируемый вызов не передается в строку поиска, поэтому необходимо как обновлять его, так и следить за этими обновлениями, а затем запускать вызов. - person Matt Morgan; 20.04.2018

Мне не удалось найти ответ на SO (или где-либо еще) для этого, но в конце концов я собрал его методом проб и ошибок, а также из связанных материалов здесь и в документации.

То, что сработало, чего я не делал, и почему

Можно получить значение напрямую, используя запрос JavaScript DOM, а также можно покопаться в структуре компонента множественного выбора и получить значение. Первое решение обходит фреймворк, второе зависит от недокументированных атрибутов компонента множественного выбора. Я избегаю обоих этих решений как неидиоматических и хрупких.

Мое текущее решение

  1. Обновил атрибут компонента всякий раз, когда в поле поиска появлялось событие изменения. Это позволило мне захватить строку запроса пользователя.
  2. Вызвал мою регулируемую асинхронную функцию из прослушивателя событий.
  3. Передал обычный function вместо функции стрелки в throttle, который дал правильный this (компонент Vue).

Если у кого-то есть предложение, как лучше сделать это в Vue 2, я все слышу.

Вот как в итоге выглядело мое решение:

<template>    
  <div>
    <label
      class="typo__label"
      for="ajax">Async select</label>
    <multiselect
      id="ajax"
      v-model="selectedWork"
      label="title"
      track-by="id"
      placeholder="Type to search"
      :options="works"
      :searchable="true"
      :loading="isLoading"
      :internal-search="false"
      :multiple="false"
      :clear-on-select="true"
      :close-on-select="true"
      :options-limit="300"
      :limit="3"
      :limit-text="limitText"
      :max-height="600"
      :show-no-results="false"
      open-direction="bottom"
      @select="redirect"
      @search-change="updateSearchTerm">
      <span slot="noResult">Oops! No elements found. Consider changing the search query.</span>
    </multiselect>
  </div>
</template>

<script>
  import {mapGetters} from 'vuex';
  import { throttle } from 'lodash';

  import Multiselect from 'vue-multiselect'

  export default {
    components: {
      Multiselect
    },
    data() {
      return {
        searchTerm: '',
        selectedWork: {},
        works: [],
        isLoading: false
      }
    },
    computed: {
      ...mapGetters(['worksList']),
    },
    methods: {
      limitText(count) {
        return `and ${count} other works`;
      },
      redirect(work) {
        // redirect to selected page
      },
      updateSearchTerm(term){
        this.searchTerm = term;
        this.isLoading = true;
        this.getWorksAsync();
      },
      getWorksAsync: throttle(function() {
        const term = this.searchTerm.toLowerCase();
        callMyAPI(term)
        .then(results => {
            this.works = results;
            this.isLoading = false;
        })
      }, 200)
    }
  }

</script>
person Matt Morgan    schedule 20.04.2018