vue 中 $emit 诡异的无法触发问题如何解决?

今日无意间发现一个 关于 vue $emit 的诡异问题: 就是 如果把 this.$emit('xx') 放入到 定时器 中或者放入到 $nextTick 中就不会触发相关的父级时间,请问各位大神有没有遇到过类似大问题?

网上也看了一些相关答案,都是说作用域啊,大小写相关的原因。

首先我的定时器和 $nextTick 均是箭头函数,不存在作用域问题,即使在外面缓存 this 传入,依然无法触发,大小写问题已经试过了,没有效果。

还在思否看到三年前有人提出了类似的问题至今还是没有得到解答真的很迷惑!还请各位大神指点!

setTimeout(() => {
    this.$emit('liquidgroup')
}, 1000);

上面这样就无法触发 很是诡异

this.$emit('liquidgroup')

这样就可以!!!

阅读 9.8k
8 个回答

貌似并没复现,demo如下。建议题主提供一下能复现的简易版demo

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <meta content="IE=edge, chrome=1" http-equiv="X-UA-Compatible">
  <meta content="webkit" name="renderer">
</head>
<body>
<div id="app">
  <test-emit @change="changeHandler"></test-emit>
</div>
</body>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script>
  Vue.component('test-emit', {
    methods: {
      clickBtn () {
        setTimeout(() => {
          console.log('触发定时器')
          this.$emit('change')
        }, 1000)
        this.$nextTick(() => {
          console.log('触发nextTick')
          this.$emit('change')
        })
      }
    },
    template: '<button @click="clickBtn">点我</button>'
  })
  
  new Vue({
    el: '#app',
    methods: {
      changeHandler () {
        console.log('触发父组件方法')
      }
    }
  })
</script>
</html>

经过我的进一步排查发下 这种问题只有在 async的异步函数中会发生 通过打出 当前vue的实例this 可发现 this是同一一个对象 只是加上定时器后的this中 refs 全都变成了undefined events 数组也变成了空数组

$attrs: (...)
$children: (2) [VueComponent, VueComponent]
$createElement: ƒ (a, b, c, d)
$el: div
$listeners: (...)
$options: {parent: VueComponent, _parentVnode: VNode, propsData: {…}, _parentListeners: {…}, _renderChildren: undefined, …}
$parent: VueComponent {_uid: 42, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: VueComponent, …}
$refs: {Term: undefined, editMore: undefined}  

上面是在定时器中打出this对象一部分 最后一行中的refs都变成了undefined

$attrs: (...)
$children: Array(2)
0: VueComponent {_uid: 922, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: VueComponent, …}
1: VueComponent {_uid: 996, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: VueComponent, …}
length: 2
__proto__: Array(0)
$createElement: ƒ (a, b, c, d)
$el: div
$listeners: (...)
$options: {parent: VueComponent, _parentVnode: VNode, propsData: {…}, _parentListeners: {…}, _renderChildren: undefined, …}
$parent: VueComponent {_uid: 42, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: VueComponent, …}
$refs: {Term: VueComponent, editMore: VueComponent}~~~~

下面这个则是正常打出的 refs是可以得到的同理 events数组也是这样

以上前提都是我这些操作都是在一个 aysnc方法中得到的 如果为普通函数则没有上述现象

我做了以下4个测试
包括异步的
1.直接定义在异步函数里调用this this是undefined 这个很明白的
2.异步里传个箭头回调函数 正常触发
3.异步里传个一般回调函数 和1一样作用域是基于异步函数的上下文的 而不是当前组件
4.异步里传个箭头回调函数 箭头函数内调用定时器 正常触发
无论是正常的还是异步的 按你的需求应该都可以实现
所以你具体调用的异步代码是什么样的? 应该是异步处理代码的问题吧

export default{ 
        name: 'views-segmentfault-questions-1010000023611723-sub',
        methods: { 
            fn () {
                console.log('调用fn')
                this.$emit('ontest')
            },
            nexttick_fn () {
                console.log('调用nexttick_fn')
                this.$nextTick(()=>{ 
                    console.log('触发nextTick')
                    this.$emit('ontest')
                })
            },
            settimeout_fn () {
                console.log('调用settimeout_fn')
                setTimeout(()=>{ 
                    console.log('触发settimeout')
                    this.$emit('ontest')
                },1000)
            },
            async_fn(){
                console.log('调用async_fn 1')
                async function test1() {
                    console.log('触发async_fn 1')
                    console.log(this)
                }
                test1()

                async function test2(fn) {
                    fn()
                }
                test2(()=>{
                    console.log('触发async_fn 2')
                    console.log(this)
                    this.$emit('ontest')
                })

                async function test3(fn) {
                    fn()
                }
                test3(function () {
                    console.log('触发async_fn 3')
                    console.log(this)
                })
                async function test4(fn) {
                    fn()
                }
                test4(()=>{
                    console.log('触发async_fn 4')
                    setTimeout(()=>{ 
                        console.log('触发async_fn 4 settimeout')
                        console.log(this)
                        this.$emit('ontest')
                    },1000)
                })
                
            }
        }
    }
新手上路,请多包涵

解决了没得,是render方式的组件吧

新手上路,请多包涵

this作用域

我今天遇到一个类似的问题,和题主的不大一样,但表现上看也是emit无效。
原因是
“原来是因为父组件用token v-if判断显示,
而子组件中改变了token,导致$emit还没执行完组件就被销毁了,$emit的操作自然就被打断了”

这样就可以了,亲测。

methods: {
  close() {
    var that = this:
    setTimeout(function() {
      that.$emit("childByValue");
    }, 3000);
  },
}

image.png

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题