Skip to content

vm.$on() from within components stores component state in vm but doesn't remove it when destroyed #3399

@groovy9

Description

@groovy9

I'm using 1.0.26.

After refactoring my project to replace $dispatch/$broadcast with an empty global Vue instance as a message bus using $on/$emit, I'm finding that if I call vm.$on() from the created() hook of a component, the component's entire state shows up in vm as vm._events.eventName[n].function scope.Closure._this.

Which is all well and good until a component gets destroyed, at which point the events it registered with vm.$on() seem to live forever and keep firing with the complete state it had just before it was destroyed.

Even if I were to add a 'deleted' flag to my components and use it to prevent them from doing anything, they're still setting in memory forever.

Is this a bug, or am I skipping a step somewhere in the component destruction process?

Activity

yyx990803

yyx990803 commented on Aug 6, 2016

@yyx990803
Member

If the event is registered on an external bus, you need to tear it down in beforeDestroy or destroyed.

If this gets repetitive, you can write a mixin to automate this.

groovy9

groovy9 commented on Aug 6, 2016

@groovy9
Author

What's the process for tearing it down? I haven't been able to find that in the docs.

yyx990803

yyx990803 commented on Aug 6, 2016

@yyx990803
Member

vm.$off

geekskai

geekskai commented on Apr 25, 2020

@geekskai

You can refer to this:
eventBus.js

/*
 * @version: v 1.0.0
 * @Github: https://github.com/GitHubGanKai
 * @Author: GitHubGanKai
 * @Date: 2020-04-03 16:45:31
 * @LastEditors: gankai
 * @LastEditTime: 2020-04-03 18:43:34
 * @FilePath: /utils/VueEventEmitter.js
 */

class EventBus {
    constructor (vue) {
        // Maintain event subscription behavior
        this.events = {}
        if (!this.events) {
            Reflect.defineProperty(this, 'events', {
                value: {},
                enumerable: false
            })
        }
        this.Vue = vue
        this.eventMapUid = {}
    }

    /**
     * Map all subscribed event types in uid to the same array
     * @param {String} uid
     * @param {String} type
     */
    setEventMapUid (uid, type) {
        if (!this.eventMapUid[uid]) {
            this.eventMapUid[uid] = []
        }
        this.eventMapUid[uid].push(type)
    }

    /**
     * Register for event subscription
     * @param {String} type
     * @param {Function} func
     * @param {Object} vm
     */
    subscribe (type, func, vm) {
        if (!this.events[type]) {
            this.events[type] = []
        }
        this.events[type].push(func)
        if (vm instanceof this.Vue) {
            this.setEventMapUid(vm._uid, type)
        }
    }

    /**
     * Post event
     * @param {String} type 
     * @param  {...any} args 
    */
    publish (type, ...args) {
        if (this.events[type]) {
            this.events[type].forEach(func => func(...args))
        }
    }

    /**
     * Remove subscription behavior under an event type
     * @param {String} type 
     * @param {Function} func 
    */
    unsubscribe (type, func) {
        // If func is passed, then find the function in the event type and delete it from the event type
        if (func && this.events[type]) {
            const index = this.events[type].findIndex(fn => fn === func)
            if (~index) {
                this.events[type].splice(index, 1)
            }
            return
        }
        // If func is not passed, all subscription behaviors under the entire event type are deleted by default!
        delete this.events[type]
    }

    /**
     * Remove all subscription behaviors under the uid component
     * @param {String} uid 
    */
    unsubscribeAll (uid) {
        const currentAllEvents = this.eventMapUid[uid] || []
        currentAllEvents.forEach(currentEvent => {
            this.unsubscribe(currentEvent)
        })
    }
}

export default {
    install (Vue, option = {}) {
        Reflect.defineProperty(Vue.prototype, '$eventBus', {
            value: new EventBus(Vue)
        })
        Vue.mixin({
            beforeDestroy () {
            // Intercept the beforeDestroy hook to automatically destroy all its subscribed events
                this.$eventBus.unsubscribeAll(this._uid)
            }
        })
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @yyx990803@groovy9@geekskai

        Issue actions

          vm.$on() from within components stores component state in vm but doesn't remove it when destroyed · Issue #3399 · vuejs/vue