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}`)
}
}
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!!
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With