Vue наступает на пит-ноты — как правильно использовать debounce

внешний интерфейс Vue.js

Мы часто выполняем некоторые операции при изменении размера страницы, например повторно отображаем компонент диаграммы, чтобы он адаптировался к текущему размеру страницы, ноwindow.resizeСобытия запускаются очень часто, поэтому мы обычно добавляем debounce для выполнения операции "debounce". Код выглядит следующим образом:

import debounce from 'lodahs/debounce'

export default {
  methods: {
    resize: debounce(function () {
      // do something
    }, 100)
  },

  mounted () {
    window.addEventListener('resize', this.resize)
  },

  beforeDestroy () {
    window.removeEventListener('resize', this.resize)
  }
}

Однако код выше имеет глубокую яму (долго ползал в яме - .-), давайте поговорим о моем процессе лазания по ямам.

Давайте посмотрим на пример

<template>
  <div id="app">
    <chart></chart>
    <chart></chart>
  </div>
</template>

<script>
const Chart = {
  template: '<span>chart</span>',

  methods: {
    resize: _.debounce(function () {
      console.log('resize')
    }, 1000 /* 时间设长一点,便于后面的观察 */)
  },

  mounted () {
    window.addEventListener('resize', this.resize)
  },

  beforeDestroy () {
    window.removeEventListener('resize', this.resize)
  }
}

new Vue({
  el: '#app',
  components: {
    Chart
  }
})
</script>

На странице есть два компонента Chart, они будут слушатьwindow.resizeсобытие, а затем вывести «resize» в консоль.

Теперь я перетаскиваю страницу, меняю ее размер,Через 1 с (задержка, установленная debounce, составляет 1 с), сколько раз консоль будет выводить «resize»?

Это не просто, не правда ли, каждый компонент диаграммы выводит по 1 разу, всего 2 раза?

Вот онлайнdemo, ты можешь пойти и поиграть,На самом деле, каждый раз, когда изменяется размер страницы, консоль выводит «изменить размер» только один раз., не странно?

отmethodsГоворя о

Предположим, мы определяем следующий метод в компоненте Chart:

{
  methods: {
    resize () {}
  }
}

Тогда есть один важный момент, который нужно прояснить в этой статье:Все экземпляры компонента Chart, вызовитеthis.resize(), в конце концов вызоветthis.$options.methods.resize(), внутри vue, когда создается экземпляр компонента, он фактически делает следующее:

// 绑定 this
this.resize = this.options.methods.resize.bind(this)

Это соотношение выглядит следующим образом:

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

Изменение размера в двух экземплярах Chart будет вызывать одну и ту же функцию устранения дребезга, поэтому, когда два компонента одновременно выполняют метод изменения размера, первый устраняется, поэтому мы видим вывод «resize» только один раз.

Отдельный метод изменения размера

из-заmethodsМетод, определенный в , является общим, что приводит к тому, что эффект устранения дребезга влияет друг на друга в компоненте, вызывая ошибки, поэтому, пока избегают совместного использования, каждое изменение размера может быть независимым друг от друга.Улучшенный код выглядит следующим образом:

<template>
  <div id="app">
    <chart></chart>
    <chart></chart>
  </div>
</template>

<script>
const Chart = {
  template: '<span>chart</span>',

  created () {
    // 将 resize 的定义从 methods 中拿到这里来
    // 这样就能保证 resize 只归某个实例拥有
    this.resize = _.debounce(function () {
      console.log('resize')
    }, 1000 /* 时间设长一点,便于后面的观察 */)
  },

  mounted () {
    window.addEventListener('resize', this.resize)
  },

  beforeDestroy () {
    window.removeEventListener('resize', this.resize)
  }
}

new Vue({
  el: '#app',
  components: {
    Chart
  }
})
</script>

улучшенdemo

Два последних слова

Внимательные друзья могут обнаружить, что нет, официальный примерv UE JS.org/v2/expensive/m…это поставить debounce вmethodsсередина. В официальном примере действительно нет проблем, потому что страница не может иметь два поля ввода одновременно, вызывая в одно и то же времяexpensiveOperationметод. Однако, если вы установите задержку дребезга немного больше, а затем быстро переключитесь между двумя полями ввода (хотя этот сценарий почти не существует), появится странное явление, о котором я упоминал в начале.