Vue.js - 从指令发出事件
Vue.js - Emit event from directive
是否可以从指令在附加该指令的组件中发出自定义事件。
我原以为它会像示例中描述的那样工作,但事实并非如此。
示例:
//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
事件处理程序。
更新二:
临时解决方案:
/*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>
所以我在 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
并以有意义的(对于开发人员)方式处理。其他解决方案,例如将回调作为参数发送,如果不深入研究 documentation/code.
有时会令人困惑且不明显
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)
您的解决方案对我不起作用。事实上 vnode.data.on 总是未定义的
触发事件的是
vnode.child.$emit('myevent');
希望对您有所帮助。
@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>
我知道这是一个老问题,但如果有人对此有疑问并且它不起作用。您可以使用 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"}
您可以发出自定义本机 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')" )
上面的答案很好,但其中一些已经过时了。这是我通过将它们集成到一个可行的 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')
来收听它。
最简单的方法就是像这样在 el
上使用 dispatchEvent
el.dispatchEvent(new Event('change'));
是否可以从指令在附加该指令的组件中发出自定义事件。
我原以为它会像示例中描述的那样工作,但事实并非如此。
示例:
//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
事件处理程序。
更新二:
临时解决方案:
/*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>
所以我在 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
并以有意义的(对于开发人员)方式处理。其他解决方案,例如将回调作为参数发送,如果不深入研究 documentation/code.
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)
您的解决方案对我不起作用。事实上 vnode.data.on 总是未定义的
触发事件的是
vnode.child.$emit('myevent');
希望对您有所帮助。
@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>
我知道这是一个老问题,但如果有人对此有疑问并且它不起作用。您可以使用 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"}
您可以发出自定义本机 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')" )
上面的答案很好,但其中一些已经过时了。这是我通过将它们集成到一个可行的 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')
来收听它。
最简单的方法就是像这样在 el
上使用 dispatchEvent
el.dispatchEvent(new Event('change'));