Vue.js - 从指令发出事件

Posted

技术标签:

【中文标题】Vue.js - 从指令发出事件【英文标题】:Vue.js - Emit event from directive 【发布时间】:2017-04-01 00:30:06 【问题描述】:

是否可以发出自定义事件指令该指令附加到的组件。

我希望它能够按照示例中的说明工作,但事实并非如此。

例子:

//Basic Directive
<script>
  Vue.directive('foo', 
    bind(el, binding, vnode) 
      setTimeout(() => 
        //vnode.context.$emit('bar'); <- this will trigger in parent
        vnode.$emit('bar');
      , 3000);
    
  );
</script>


//Basic Component
<template>
  <button v-foo @bar="change">label</button>
</template>
<script>
  export default
    data() 
      return 
        label: 'i dont work'
      
    ,
    methods: 
      change() 
        this.label = 'I DO WORK!';
      
    
  
</script>

对这个问题有什么想法吗?我错过了什么吗?

JSFiddle:https://jsfiddle.net/0aum3osq/4/

更新 1:

好的,我发现如果我在指令中调用 vnode.data.on.bar.fn();(或最新 Vue 版本中的 fns()),它将触发 bar 事件处理程序。

更新 2:

临时解决方案:

  /*temp. solution*/
  var emit = (vnode, name, data) => 
    var handlers = vnode.data.on;

    if (handlers && handlers.hasOwnProperty(name)) 
      var handler = handlers[name];
      var fn = handler.fns || handler.fn;

      if (typeof fn === 'function') 
        fn(data);
      
    
   

//Basic Directive
<script>
  Vue.directive('foo', 
    bind(el, binding, vnode) 
      setTimeout(() => 
        emit(vnode, 'bar');
      , 3000);
    
  );
</script>

【问题讨论】:

【参考方案1】:

所以我在 Vue 2+ 中使用的解决方案(考虑到到目前为止还没有答案):

在指令中添加方法:

var emit = (vnode, name, data) => 
  var handlers = (vnode.data && vnode.data.on) ||
    (vnode.componentOptions && vnode.componentOptions.listeners);

  if (handlers && handlers[name]) 
    handlers[name].fns(data);
  

这样称呼它:

bind(el, binding, vnode) 
  emit(vnode, 'bar' , some: 'event', data: 'here');

方法的好处:

1 在您的项目中保持相同的代码样式,这意味着每个处理程序都可以声明为 v-on:handler_name 并以有意义的(对于开发人员)方式进行处理。其他解决方案,例如将回调作为参数发送,如果不深入研究文档/代码,有时会令人困惑且不明显。

2 使用内置事件系统还可以优雅地处理事件对象。例如,这段代码可以正常工作:

<button v-foo @bar="bar(1, $event, 2)">label</button>
...
methods: 
  bar(one, event, two)  console.log(one, event, two); 
 

编辑:

在 v2.1+ 中,您可以在指令绑定中使用这个:

vnode.context.$emit(eventname)

【讨论】:

效果很好!您的发出函数调用缺少结束括号。我试图编辑,但 SO 说我必须编辑 6 个字符。 vnode.context.$emit() 在 vue@v2.5 下的这个例子中不起作用,如何解决?但是 emit 函数仍然运行良好。 根据我的经验,您只能在发送事件后调用vnode.context.$emit('name')...element.addEvenetListener('click', () =&gt; vnode.context.$emit('handleClick'); ); 有效!但是在事件触发器之外做一个简单的vnode.context.$emit('handleClick') 实际上并没有正确调用发射,因此你无法监听它。我可能是错的,但这就是我刚刚在 vue@2.5 中所经历的。 只是我,还是父级发出事件,而不是带有指令的实际元素? 这可以解释为什么它没有从正确的元素发出它。谢谢!【参考方案2】:

您的解决方案对我不起作用。事实上 vnode.data.on 总是未定义的

触发事件的原因是

 vnode.child.$emit('myevent');

希望这会有所帮助。

【讨论】:

给了我未定义的; vnode.context.$emit 至少是 Vue v.2.1.10 中的一个函数 vnode.child.$emit('bar'); Vue v2.4.2 的 $emit 未定义。 jsfiddle.net/Jimsan/vuv80dpn/7【参考方案3】:

我知道这是一个老问题,但如果有人对此有问题并且它不起作用。您可以使用使用 javascript 自定义事件事件。

    vue.directive('click',bind(el, binding, vnode) 
        el.addEventListener('click', (e)=>
            const event = new CustomEvent('customevent', detail: 
                                                          custom: "data", 
                                                          can: "be", 
                                                          in: "detail property", bubbles: true);
            el.dispatchEvent(event);
        )
    
)

现在我可以像这样使用它了

<div v-click @customevent="func">hello world</div>

我不必设置$event,因为默认是作为最后一个参数发出的标准。此事件有一个detail 属性,在这种情况下包含您的自定义数据此对象:

custom: "data", 
 can: "be", 
 in: "detail property"

src https://github.com/vuejs/vue/issues/7147

【讨论】:

【参考方案4】:

上面的答案很好,但其中一些已经过时了。这是我通过将它们集成到可行的 POC 中来解决问题的方法。

// src/directives/ClickOutside.js
export default 
  stopProp(e) 
    e.stopPropagation();
  ,
  bind(el, binding, vnode) 
    el._clickOutside = e => 
      vnode.context.$emit(binding.expression, e);
    ;
    el.addEventListener('click', binding.def.stopProp);
    document.body.addEventListener('click', el._clickOutside);
  ,
  unbind() 
    if (!el._clickOutside) 
      return;
    
    el.removeEventListener('click', binding.def.stopProp);
    document.body.removeEventListener('click', el._clickOutside);
    delete el._clickOutside;
  
;

// src/directives/index.js
import Vue from 'vue';
import ClickOutside from './ClickOutside';

Vue.directive('ClickOutside', ClickOutside);

在 main.js 中导入指令:

// src/main.js
import './directives';

使用该指令监听 Vue 组件中的事件发射:

// src/components/Component.vue
<template>
  <!-- Please fill in sensible context. This example doesn't really care about the DOM presentation -->
  <div @click="showElement" v-click-outside="hideElement">
    <div v-if="shouldShow">Hello</div>
  </div>
</template>

<script>
export default 
  data() 
    return 
      shouldShow: true
    ;
  ,
  mounted() 
    this.$on('hideElement', this.hideElement);
  ,
  destroyed() 
    this.$off('hideElement', this.hideElement);
  ,
  methods: 
    showElement() 
      this.shouldShow = true;
    ,
    hideElement() 
      this.shouldShow = false;
    
  
;
</script>

基本上,在vnode.context.$emit 中,binding.expression 是您在v-close-outside 中声明的字符串(即本例中的“hideElement”)。要从指令中检索发射,请使用this.$on('hideElement') 来收听它。

【讨论】:

【参考方案5】:

最简单的方法就是像这样在el上使用dispatchEvent

el.dispatchEvent(new Event('change'));

【讨论】:

【参考方案6】:

您可以发出自定义的原生 javascript 事件。使用 node.dispatchEvent 创建一个从节点调度事件的指令

let handleOutsideClick;
Vue.directive('out-click', 
    bind (el, binding, vnode) 

        handleOutsideClick = (e) => 
            e.stopPropagation()
            const handler = binding.value

            if (el.contains(e.target)) 
                el.dispatchEvent(new Event('out-click')) <-- HERE
            
        

        document.addEventListener('click', handleOutsideClick)
        document.addEventListener('touchstart', handleOutsideClick)
    ,
    unbind () 
        document.removeEventListener('click', handleOutsideClick)
        document.removeEventListener('touchstart', handleOutsideClick)
    
)

可以这样使用

h3( v-out-click @click="$emit('show')" @out-click="$emit('hide')" )

【讨论】:

【参考方案7】:

@euvl 的解决方案很好,但我认为将函数作为参数传递给您的指令更容易和更清晰。似乎也简化了指令的接口。

<script>
  Vue.directive('foo', 
    bind(el, binding) 
      setTimeout(() => 
        binding.value();
      , 3000);
    
  );
</script>

<template>
  <button v-foo="change">label</button>
</template>

<script>
  export default
    data() 
      return 
        label: 'i dont work'
      
    ,
    methods: 
      change() 
        this.label = 'I DO WORK!';
      
    
  
</script>

【讨论】:

恐怕这不能回答问题。另外,例如,如果您有指令v-draggable,并且您希望有事件on-drag-starton-drag-endon-dragon-drag-error,我认为您的方法不会那么干净。好电话。 @euvl 我想这有点取决于指令(OP 不是很具体)。我想你也可以给回调一个参数,但是你正在以某种方式重新构建事件,所以你的解决方案可能会更干净。

以上是关于Vue.js - 从指令发出事件的主要内容,如果未能解决你的问题,请参考以下文章

Vue JS:右键单击事件指令

全栈工程师开发实战之从入门到技术实战之02--vue指令

vue.js实战学习——指令与事件

vue.js介绍,常用指令,事件,以及制作简易留言版

Vue.js常用指令:v-on

Vue.js高效前端开发 • Vue基本指令