如何动态添加Vue生命周期侦听器

时间:2018-02-22 16:03:11

标签: javascript vuejs2

我有一些功能,我需要能够在vue组件被销毁时调用,但在创建事件之前我不一定知道它们是什么。

有没有办法动态添加监听器到vue生命周期事件?

我想要实现的目标:

...methods: {
    logOnDestroy(txt) {
        this.$on('beforeDestroy', () => {
            console.log(txt)
        }
    }
}

但目前尚未调用。是否有不同的方法以在运行时以编程方式将侦听器绑定到组件生命周期事件?

6 个答案:

答案 0 :(得分:3)

每个生命周期事件的处理程序数组存储在this.$options对象中。你可以通过推送到相应的数组来添加一个处理程序(如果没有设置处理程序,你需要先创建数组):



new Vue({
  el: '#app',
  created() {
    if (!this.$options.mounted) {
      this.$options.mounted = [];
    }
  
    this.$options.mounted.push(() => {
      console.log('mounted')
    });
    
    this.$options.mounted.push(() => {
      console.log('also mounted')
    });
  }
})

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>

<div id="app">
  <div></div>
</div>
&#13;
&#13;
&#13;

所以在你的情况下:

methods: {
  logOnDestroy(txt) {
    if (!this.$options.beforeDestroy) {
      this.$options.beforeDestroy = [];
    }

    this.$options.beforeDestroy.push(() => {
      console.log(txt)
    });
  }
}

答案 1 :(得分:2)

您可能会问,它会更简单吗?

Vue.js Component Hooks as Events中,这是您要寻找的语法

this.$once('hook:beforeDestroy', () => {

我不确定您打算如何使其动态化,但这是您在Vue CLI的默认HelloWorld应用中对logOnDestroy()方法的改编,

演示

Vue.component('helloworld', {
  template: '<h1>{{ msg }}</h1>',
  name: 'helloworld',
  props: { msg: String },
  mounted() {
    this.logOnDestroy('Goodbye HelloWorld')
  },
  methods: {
    logOnDestroy(txt) {
      this.$once('hook:beforeDestroy', () => {
        console.log(txt)
      })
    }    
  }
});

new Vue({
  el: '#app',
  data: {
    showHello: true
  },
  mounted() {
    setTimeout(() => {
      this.showHello = false
    }, 3000)
  }
});
Vue.config.productionTip = false
Vue.config.devtools = false
<script src="https://unpkg.com/vue"></script>
<div id="app">
  <img alt="Vue logo" src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/277px-Vue.js_Logo_2.svg.png" style="height: 50px;">
  <helloworld v-if="showHello" msg="Welcome to Your Vue.js App"/>
</div>

答案 2 :(得分:1)

一个简单的解决方案是简单地跟踪要在组件内添加的所有动态处理程序:

&#13;
&#13;
Vue.component('foo', {
  template: '<div>foo component</div>',
  data() {
    return {
      beforeDestroyHandlers: []
    }
  },
  created() {
    this.beforeDestroyHandlers.push(() => {
      console.log('new handler called');
    });
  },
  beforeDestroy() {
    this.beforeDestroyHandlers.forEach(handler => handler());
  }
});

new Vue({
  el: '#app',
  data: {
    includeComponent: false
  }
});
&#13;
<script src="https://unpkg.com/vue"></script>

<div id="app">
  <button v-on:click="includeComponent = true">Add component</button>
  <button v-on:click="includeComponent = false">Destroy component</button>
  <foo v-if="includeComponent"></foo>
</div>
&#13;
&#13;
&#13;

答案 3 :(得分:1)

2019年1月-有关该问题答案的警告-使用此代码添加动态侦听器时,我发现了一个细微问题。

this.$options.beforeDestroy.push(() => {
  console.log(txt)
});

在未定义静态beforeDestroy的情况下可以正常工作。在这种情况下,handlers数组是$options的直接属性。

但是,如果您在组件上定义了静态beforeDestroy钩子,则handlers数组是$options.__proto__的属性,这意味着组件的多个实例继承了先前实例的动态处理程序(实际上,上述代码修改用于创建连续实例的模板。

我不确定这是多少实际问题。看起来很糟糕,因为处理程序数组在您浏览应用程序时会变大(例如,每次切换页面都会添加一个新功能)。


添加动态处理程序的一种更安全的方法是使用此injectHook代码,Vue使用该代码进行热模块重载(您可以在运行的Vue应用程序的index.js中找到它)。注意,我正在使用Vue CLI 3。

function injectHook(options, name, hook) {
  var existing = options[name]
  options[name] = existing
    ? Array.isArray(existing) ? existing.concat(hook) : [existing, hook]
    : [hook]
}
...
injectHook(this.$options, 'beforeDestroy', myHandler)

这里发生的是在实例上 创建了一个新数组,其中包含__proto__中的所有处理程序以及新的处理程序。旧数组仍然存在(未修改),新数组与实例一起被销毁,因此__proto__处理程序数组中没有处理程序堆积。

答案 4 :(得分:0)

Eric99的回答很有启发性,但是我觉得比摆弄Vue的内部结构更容易实现相同的目标:在组件上使用事件。更容易阅读,更容易编写。

Vue.component('foo', {
  methods {
    logOnDestroy (txt) {
      this.$on('beforeDestroy', () => {
        console.log(txt)
      })
    }
  },
  beforeDestroy () {
    this.$emit('beforeDestroy');
  }
});

您可能会问:发出事件后是否需要调用this.$off()来防止内存泄漏?答案是否定的,Vue会自动为您执行此操作,因此上面的代码很好。

答案 5 :(得分:0)

Eric99 的回答很好,但它不适用于像 beforeRouteLeavebeforeRouteUpdatebeforeRouteEnter 这样的组件内导航守卫——它们由 VueRouter 维护(并在相应组件的编译)。由于 Vue-Router 缓存了组件的构造函数并使用它而不是实际的组件实例,我们的生活变得更加困难。

为了解决这个问题,我不得不深入研究 VueRouter 的内部结构,但我想出了以下代码(_Ctor 是缓存的构造函数 - 我还没有检查是否只使用它就足够了,所以为了安全起见,我使用组件定义和构造函数):

const routeComponent = this.$route.component;
injectHook([
  routeComponent, 
  routeComponent._Ctor && routeComponent._Ctor[0] 
    ? routeComponent._Ctor[0].options 
    : null
], 'beforeRouteLeave', this.hookFunction);

function injectHook(routeComponentInstance, hookName, hookFunction)
{
  (Array.isArray(routeComponentInstance) 
    ? routeComponentInstance 
    : [routeComponentInstance]
  ).forEach(instance =>
  {
    if (instance)
    {
      const existing = instance[hookName];
      if (existing && Array.isArray(existing))
      {
        const index = existing.findIndex(item => item === hookFunction);
        if (index < 0) return;
      }
      instance[hookName] = existing
        ? Array.isArray(existing) ? existing.concat(hookFunction) : [existing, hookFunction]
        : [hookFunction];
    }
  });
}

function removeHook(routeComponentInstance, hookName, hookFunction)
{
  (Array.isArray(routeComponentInstance) 
    ? routeComponentInstance 
    : [routeComponentInstance]
  ).forEach(instance =>
  {
    if (instance)
    {
      const existing = instance[hookName];
      if (existing && Array.isArray(existing))
      {
        const index = existing.findIndex(item => item === hookFunction);
        if (index !== -1) existing.splice(index, 1);
      }
    }
  });
}