在 vue js 中有条件地绑定自定义指令 'clicking outside an element event'

Conditionally bind custom-directives in vue js for 'clicking outside an element event'

参考资料

我正在为 'click-outside senario' 编写元素列表的自定义指令。

基本上,当在列表中的项目上单击按钮时,它会进入选定模式。现在,如果在其他任何地方发生点击,我需要取消选择模式。 为此,我需要检测 click outside 。 我从中找出了它的指令 为此,我想出了

  const clickOutside = {
  bind: function (el, binding, vnode) {
    console.log('bind called')

    document.body.addEventListener('click', (event) => {
      // check that click was outside the el and his childrens
      if (!(el == event.target || el.contains(event.target))) {
        // and if it did, call handle method provided in attribute value
        console.log('directive working')
        vnode.context[binding.expression](event);
      }
    })
  },
  unbind: function (el) {
    document.body.removeEventListener('click', el.event)
    console.log('unbind called')
  }
}
export {
  clickOutside
}

来自上面的参考

现在我只希望每个列表项在处于选中模式时监听外部点击。

所以我需要完成类似

的事情
<div id="list-item"  v-on-click-outside="outSideClickHandler" //trigger only when selected>
</div>

<script>
export default{
data:{
selectedState:false;
},
methods:{
outSideClickHandler:{//......}
}
</script>

目前没有简单的方法来进行条件指令绑定。您可能会考虑使用 v-if.

   <div v-for='item of list'>
        <div
          v-if='item.selected'
          id="list-item" 
          v-on-click-outside="outSideClickHandler"
        />
        <div v-else
          id="list-item" 
          v-on-click-outside="outSideClickHandler"
        />
    </div>

另一种方法是修改您的指令实现,以便它接受另一个活动的布尔标志以在 eventListener 中选择退出。

<div id="list-item"  v-on-click-outside="{handler: outSideClickHandler, active: item.selected}"  />

为什么不在点击外部处理程序中进行选定的检查?您还需要一种将点击的项目传递给处理程序的方法。

<div id="list-item" v-on-click-outside="outSideClickHandler(item)"></div>
outSideClickHandler(item) {
  return event => {
    if (item.selected) {
      // Handle the click outside
    }
  };
}

像这样在指令中调用处理程序:

binding.value(event);

您不会像使用 v-on 那样获得自定义指令的自动 "expression/statement" 绑定,这就是为什么当您想要向它传递额外参数时需要柯里化处理函数。

我的意思是传递给 v-on 的参数可以是表达式或语句,例如:

@click="handler"        - handler is an expression (the function itself)
@click="handler(item)"  - handler(item) is a statement

但是对于自定义指令,您只能传递表达式;上面的第二行是不可能的,除非 handler() returns 另一个函数。


我认为有些混乱,因为您似乎想要一个自定义指令,该指令仅在您的列表项的这种特定情况下使用,但我上面的解决方案更多是关于编写一个通用的 "click outside" 你可以在任何情况下使用的指令。

此外,如果未选择列表项(出于性能原因?),我认为您不希望指令注册任何事件侦听器。如果是这种情况,那么您可以改用事件委托。

没有办法有条件地 enable/disable 一个指令,你必须做类似 的事情,这两者都有点混乱。

This seems workable but the whole point of using custom directives is to write reusable dom manipulation code

您是否反对在指令之外编写 DOM 操作代码? Angular 1 有这种哲学。除非你想在不同的情况下重用指令,否则为这种情况编写指令只是为了 "DOM manipulation code does not pollute my component" 可能有点矫枉过正。如果我要编写一个指令,那么我希望它尽可能通用,以便我可以在许多不同的情况下使用它。

I don't even need to pass the item in that case. Cause I have a component inside a v-for and not a div and I bind the custom directive on that component of which the handler is a method

那么我不确定您为什么要将此作为指令来实现,反正很少需要它。您可以只在 mounted 挂钩中注册 body click 事件,然后在 destroyed 挂钩中将其删除。所有的点击外部逻辑都可以包含在组件本身中。


如果您主要关心的是不必为每个列表项注册正文点击事件侦听器,您可以这样做:

const handlers = new Map();

document.addEventListener('click', e => {
    for (const handler of handlers.values()) {
        handler(e);
    }
});

Vue.directive('click-outside', {
    bind(el, binding) {
        const handler = e => {
            if (el !== e.target && !el.contains(e.target)) {
                binding.value(e);
            }
        };

        handlers.set(el, handler);
    },

    unbind(el) {
        handlers.delete(el);
    },
});

您可以更进一步,当 handlers 为非空时自动添加 body click 事件侦听器,并在 handlers 为空时删除侦听器。由你决定。