删除取消绑定Vue指令生命周期上的窗口事件侦听器

yrdbyhpb  于 2023-03-09  发布在  Vue.js
关注(0)|答案(3)|浏览(191)

我刚刚遇到了一个与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来实现这个行为,这样每当路径改变时它都会被重新渲染。但问题是事件侦听器不会被删除/销毁,从而导致严重的性能问题。那么我如何删除事件侦听器呢?

omtl5h9j

omtl5h9j1#

在函数上调用bind会创建一个新函数。侦听器不会被删除,因为传递给removeEventListener的函数与传递给addEventListener的函数不同。
指令中钩子之间的通信并不是特别容易,官方文档推荐使用元素的dataset,尽管在这种情况下这看起来很笨拙:
https://v2.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>

然而,所有这些都假设一个指令是正确的方法。如果你可以在组件级别上这样做,那么它可能会变得简单得多,因为你有组件示例可以用来存储东西。只要记住,调用bind会创建一个新函数,所以你需要在某个地方保留一个对该函数的引用,这样你就可以把它传递给removeEventListener

mrfwxfqh

mrfwxfqh2#

仅作记录,并且为了帮助任何经过这里的人,因为已经有一个可接受的答案,在这种情况下(至少在Vue 3上,没有在Vue 2上测试),可以使用binding.dir(它是对指令自身对象的引用)来托管在指令对象上添加事件侦听器的函数,并在以后需要删除此侦听器时将其收回。
绑定一个焦点事件的一个简单示例(与原始问题无关):

export default {
  ...
  directives: {
    fox: {
      handleFocus: () => { /* a placeholder to rewrite later */ },
      mounted(el, binding) {
        binding.dir.handleFocus = () => { /* do whatever */ }
        el.addEventListener('focus', binding.dir.handleFocus);
      },
      beforeUnmount(el, binding) {
        el.removeEventListener('focus', binding.dir.handleFocus);
      }
    }
  }
  ...
}

在我的例子中,我用这个做了一个实际的例子,就是为任何输入或文本区域标记提供一个焦点/模糊通知器。我为此做了一个Gist here,它是在一个使用TypeScript构建在Vue 3上的项目上。

eqoofvh9

eqoofvh93#

下面是我的解决方案(在chatgpt的帮助下),用于在窗口上添加/删除事件侦听器,在本例中是一个调整大小处理程序:

.directive('resize', {
    beforeMount(el, binding, vnode) {
      const onResizeCallback = binding.value;
      window.addEventListener('resize', () => {
        const width = document.documentElement.clientWidth;
        const height = document.documentElement.clientHeight;
        onResizeCallback({ width, height });
      }, { passive: true });
      // eslint-disable-next-line no-underscore-dangle, no-param-reassign
      el._resizeListener = binding.value;
    },
    unmounted(el) {
      // eslint-disable-next-line no-underscore-dangle
      window.removeEventListener('resize', el._resizeListener);
    },
  })

相关问题