Удалите прослушиватель событий окна при жизненном цикле директивы unbind Vue - PullRequest
0 голосов
/ 10 февраля 2020

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

function setHeaderWrapperHeight() { ... }
function scrollEventHandler() { ... }

export default {
  ...
  directives: {
    fox: {
      inserted(el, binding, vnode) {
        setHeaderWrapperHeight(el);
        el.classList.add('header__unfixed');
        window.addEventListener(
          'scroll',
          scrollEventListener.bind(null, el, binding.arg)
        );
        window.addEventListener(
          'resize',
          setHeaderWrapperHeight.bind(null, el)
        );
      },
      unbind(el, binding) {
        console.log('Unbound');
        window.removeEventListener('scroll', scrollEventListener);
        window.removeEventListener('resize', setHeaderWrapperHeight);
      }
    }
  }
  ...
}

И этот компонент перерисовывается каждый раз, когда я изменяю путь к маршрутизатору, я добивался этого поведения, назначая текущий путь маршрута для :key prop, чтобы при каждом изменении пути это перерисовывается. Но проблема в том, что слушатели событий не удаляются / не уничтожаются, что вызывает ужасные проблемы с производительностью. Итак, как мне удалить слушателей событий?

1 Ответ

0 голосов
/ 10 февраля 2020

Вызов bind для функции создает новую функцию. Слушатели не удаляются, потому что функция, которую вы передаете removeEventListener, - это не та функция, которую вы передали addEventListener.

. Связь между перехватчиками в директивах не особенно проста. Официальная документация рекомендует использовать элемент dataset, хотя в этом случае это выглядит неуклюже:

https://vuejs.org/v2/guide/custom-directive.html#Directive -Hook-Arguments

Вы можете просто сохранить слушатели для элемента непосредственно в качестве свойств, чтобы они были доступны в хуке unbind.

Код ниже использует несколько иной подход. Он использует массив для хранения всех элементов, которые в настоящее время связаны с директивой. Слушатель на window регистрируется только один раз, независимо от того, сколько раз используется директива. Если директива в настоящее время не используется, этот слушатель удаляется:

let foxElements = []

function onClick () {
  console.log('click triggered')

  for (const entry of foxElements) {
    clickHandler(entry.el, entry.arg)
  }
}

function clickHandler (el, arg) {
  console.log('clicked', el, arg)
}

new Vue({
  el: '#app',
  
  data () {
    return {
      items: [0]
    }
  },

  directives: {
    fox: {
      inserted (el, binding) {
        console.log('inserted')
        
        if (foxElements.length === 0) {
          console.log('adding window listener')
          window.addEventListener('click', onClick)
        }

        foxElements.push({
          el,
          arg: binding.arg
        })
      },

      unbind (el, binding) {
        console.log('unbind')
      
        foxElements = foxElements.filter(element => element.el !== el)
        
        if (foxElements.length === 0) {
          console.log('removing window listener')
          window.removeEventListener('click', onClick)
        }
      }
    }
  }
})
<script src="https://unpkg.com/vue@2.6.11/dist/vue.js"></script>

<div id="app">
  <button @click="items.push(Math.floor(Math.random() * 1000))">Add</button>
  <hr>
  <button
    v-for="(item, index) in items"
    v-fox:example
    @click="items.splice(index, 1)"
  >Remove {{ item }}</button>
</div>

Однако все это предполагает, что директива является даже верным путем к go. Если вы можете просто сделать это на уровне компонентов, то это может стать намного проще, потому что у вас есть экземпляр компонента, доступный для хранения вещей. Просто помните, что вызов bind создает новую функцию, поэтому вам нужно где-то хранить ссылку на эту функцию, чтобы вы могли передать ее в removeEventListener.

...