Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Global event bus using Vuex - always notify subscribers

I've been using a global event bus for quite some time in Vue - something like const bus = new Vue(). Works fine, however, disposal of subscriptions can become quite verbose.

Let's say that I subscribe to an event in a component:

mounted() {
  bus.$on('some.event', callback)
}

I would have to keep track of the callback and dispose it properly in beforeDestroy. This can be somewhat simplified using a global mixin, but since I'm using <keep-alive>, I have to differentiate between subscriptions made in mounted and activated callback.

So I figured I would give Vuex a shot at managing this, since watchers are disposed by the framework. I came up with the proposal below.

Seems to work fine as long as objects or arrays are published. Primitive data doesn't seem to trigger the reactivity, despite being wrapped in the outer object, i.e. { data: 123 }

I'm looking for alternative solutions regarding notifying the subscribers. All I've seen so far is the internal notify method, which doesn't feel very safe to use.

eventstore.js

import Vue from 'vue'

const state = {
  events: {}
}

const actions = {
  publish({commit}, payload) {
    commit('publish_event', payload)
  }
}

const mutations = {
  publish_event(state, payload) {
    if(!state.events[payload.key]) {
      Vue.set(state.events, payload.key, { data: payload.data })
    } else {
      state.events[payload.key] = { data: payload.data }
    }
  }
}

const getters = {
  events: state => state.events
}

export default {
  state,
  actions,
  mutations,
  getters
}

globalmixin.js

methods: {
  publish(key, data) {
    this.$store.dispatch('publish', { key, data })
  }
}

somecomponent.vue

function mapEventGetters(eventKeys) {
  return _.reduce(eventKeys, (result, current) => {
    result[current] = function() {
      return  _.get(this, `$store.getters.events.${current}.data`)
    }
    return result
  }, {})
}
computed: {
  ...mapEventGetters(['foo_bar'])
},
watch: {
  'foo_bar'(value) {
    console.log(`foo_bar changed to ${value}`)
  }
}
like image 285
Johan Avatar asked Dec 03 '18 16:12

Johan


2 Answers

This API will break the Vuex's data flow which is the core concept of Vuex. The clients would able to mutate/read store state everywhere in Vuex.

Honestly this way is not needed to be implemented in the Vuex since it is just an event emitter. I suggest you to use some event emitter (probably empty Vue instance) in actions.

export const emitter = new Vue()

export default {
  // ...

  actions: {
    // should be called when the store is initialized
    // to observe events
    observe({ dispatch, commit }) {
      emitter.$on('some-event', () => {
        commit('someEvent')
      })

      emitter.$on('other-event', () => {
        dispatch('otherEvent')
      })
    },

    // notify some event in action
    notify({ state }) {
      emitter.$emit('notify', state.someValue)
    }
  }
}

It solve my problem once when I search in github. Mat be help you. Thanks!!

like image 130
Ismoil Shifoev Avatar answered Nov 18 '22 00:11

Ismoil Shifoev


You can use deepCopy (for example JSON.parse(JSON.stringify())) to make sure data is reactive

const mutations = {
  publish_event(state, payload) {
    if(!state.events[payload.key]) {
      state.events[payload.key] = { data: payload.data }
    } else {
      state.events[payload.key] = Object.assign({}, state.events[payload.key], { data: payload.data })
    }
    state.events = JSON.parse(JSON.stringify(state.events))
  }
}

In your component above, you're listening for foo_bar in watcher. Vue watcher only work with component data (from data, computed or vuex).

You can redefine your data as componentData as below. You can use mapGetters for shorter syntax:

<script>
  import { mapGetters } from 'vuex'
  export default {
    ...mapGetters(['events']),
    computed: {
      componentData () {
        const eventKeys = ['foo_bar']
        return _.reduce(eventKeys, (result, current) => {
          result[current] = function() {
            return  _.get(this, `events.${current}.data`)
          }
          return result
        }, {})
      }
    },
    watch: {
      componentData: function (newVal, oldVal) {
        ...
      }
    }
  }
</script>
like image 2
ittus Avatar answered Nov 18 '22 02:11

ittus