有条件地在 vue js 中绑定自定义指令以“在元素事件之外单击”

Posted

技术标签:

【中文标题】有条件地在 vue js 中绑定自定义指令以“在元素事件之外单击”【英文标题】:Conditionally bind custom-directives in vue js for 'clicking outside an element event' 【发布时间】:2018-10-21 14:58:27 【问题描述】:

参考文献

Add vue directive on condition

Detect click outside element

我正在为元素列表的“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>

【问题讨论】:

【参考方案1】:

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

<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 那样获得自定义指令的自动“表达式/语句”绑定,这就是为什么当您想要向其传递额外参数时需要对处理函数进行 curry。

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

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

但是使用自定义指令你只能传递表达式;除非handler() 返回另一个函数,否则上面的第二行是不可能的。


我认为存在一些混淆,因为您似乎想要一个自定义指令,该指令仅在这种特定情况下与您的列表项一起使用,但我上面的解决方案更多的是关于编写一个通用的“点击外部”指令,该指令您可以在任何情况下使用。

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

没有办法有条件地启用/禁用指令,您必须执行Morty's answer 之类的操作,这两者都有些混乱。

这似乎可行,但使用自定义指令的全部意义在于编写可重用的 dom 操作代码

您反对在指令之外编写 DOM 操作代码吗? Angular 1 有这个理念。除非您想在不同的情况下重用该指令,否则为这种情况编写一个指令可能只是为了“DOM 操作代码不会污染我的组件”。如果我要编写一个指令,那么我希望它尽可能通用,以便我可以在许多不同的情况下使用它。

在这种情况下,我什至不需要传递该项目。因为我在 v-for 而不是 div 中有一个组件,并且我将自定义指令绑定到处理程序是方法的那个组件上

那么我不知道你为什么要将它作为一个指令来实现,反正很少需要它。您可以在 mounted 挂钩中注册正文单击事件,然后在 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 为非空时自动添加正文单击事件侦听器,并在handlers 为空时删除侦听器。这取决于你。

【讨论】:

似乎outSideClickHandler(item) 将在指令绑定时正确评估。我觉得应该是v-on-click-outside="() =&gt; outSideClickHandler(item)" @MortyChoi 是的,它会,但它会returns another function。 好的。我现在看到了。谢谢。 这似乎可行,但使用自定义指令的全部目的是编写可重用的 dom 操作代码,我虽然通过传递诸如何时激活指令的标志来保持它更通用,但在这种情况下当该标志的值发生变化时,我需要倾听 @SainathS.R 如果您想使用 v-on:click 而不是自定义指令,我的回答与您所做的没有太大区别,所以我不确定您如何认为这是不可重用的DOM 操作代码——它不对应该启用它的情况做任何假设。您是说您希望 enable-if-selected 行为成为指令的一部分吗?这更不通用。【参考方案2】:

目前没有简单的方法来进行条件指令绑定。您可以考虑使用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"  />

【讨论】:

以上是关于有条件地在 vue js 中绑定自定义指令以“在元素事件之外单击”的主要内容,如果未能解决你的问题,请参考以下文章

vue-常用指令&条件渲染&列表渲染

vue.js自定义指令入门

vue.js实现内部自定义指令和全局自定义指令------directive

Vue基础(下):条件渲染列表渲染(key)过滤器内置和自定义指令

什么是vue.js中的自定义指令?

Vue.js自定义指令的用法与实例