【问题标题】:Global event bus using Vuex - always notify subscribers使用 Vuex 的全局事件总线 - 始终通知订阅者
【发布时间】:2019-05-04 23:53:39
【问题描述】:

我在 Vue 中使用全局事件总线已经有一段时间了——比如const bus = new Vue()。工作正常,但是订阅的处理可能会变得非常冗长。

假设我订阅了组件中的一个事件:

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

我必须跟踪回调并在beforeDestroy 中正确处理它。这可以使用全局 mixin 稍微简化,但由于我使用的是 <keep-alive>,因此我必须区分在 mountedactivated 回调中进行的订阅。

所以我想我会给 Vuex 一个机会来管理这个,因为观察者是由框架处理的。我提出了以下建议。

只要发布了对象或数组,似乎就可以正常工作。原始数据似乎不会触发反应,尽管被包裹在外部对象中,即{ data: 123 }

我正在寻找有关通知订阅者的替代解决方案。到目前为止我所看到的只是内部的notify 方法,使用起来感觉不太安全。

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}`)
  }
}

【问题讨论】:

  • 什么是“内部通知方法”?指的是观察者吗?
  • 好的,谢谢。这没有出现在 API 文档中,所以我同意使用起来可能很狡猾(ob = this.__ob__ 也许它是一个可以改变的内部?)
  • 我只是在组件中使用计算属性来代替观察者或显式订阅。为了获得对商店项目的深度引用,我将添加一个提取深度值的商店 getter。
  • @RichardMatsen 确实,依赖内部结构似乎是个坏主意 :)

标签: javascript vue.js vuex


【解决方案1】:

您可以使用 deepCopy(例如 JSON.parse(JSON.stringify()))来确保数据是响应式的

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))
  }
}

在上面的组件中,您正在监听器中监听foo_barVue watcher 仅适用于组件数据(来自 datacomputedvuex)。

您可以将您的数据重新定义为componentData,如下所示。您可以使用 mapGetters 来获得更短的语法:

<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>

【讨论】:

  • "Vue watcher 仅适用于组件数据(来自数据、计算或 vuex)。"。 foo_bar 是从 vuex 计算出来的
【解决方案2】:

这个 API 将打破 Vuex 的数据流,这是 Vuex 的核心概念。客户端将能够在 Vuex 中随处改变/读取存储状态。

老实说,这种方式不需要在 Vuex 中实现,因为它只是一个事件发射器。我建议你在动作中使用一些事件发射器(可能是空的 Vue 实例)。

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)
    }
  }
}

当我在 github 中搜索时,它一次解决了我的问题。可以帮助你。谢谢!!

【讨论】:

  • 这里的答案很好。我相信从组件发出事件时遇到了麻烦,因为商店是如何命名的。创建一个作为包装器的动作解决了这个问题。不知道为什么我没想到。
【解决方案3】:

在对象上调用Vue.set 不会向对象内的数据添加观察者/反应性。这需要一个额外的 Vue.set

Vue.set(state.events, payload.key, {})
Vue.set(state.events[payload.key], 'data', payload.data)

您还可以将其包装到一个实用函数中,该函数使用Vue.set 递归设置数据

【讨论】:

    【解决方案4】:

    你能试试这个,如果在这两种情况下都触发了反应,请告诉我?

    首先,用外部对象移除这个不必要的包装,并将有效负载作为简单的键/值对象发送,并带有所需的事件键和该键的数据:

    {
      someKey: 123
    }
    

    其次,发送一些嵌套数据:

    {
      someKey: {
        nested: 'Value'
      }
    }
    

    但在此之前,更改突变代码如下:

    const mutations = {
      publish_event(state, payload) {
        // Instead of the previous code, just "patch"
        // the state.events with the payload content.
        state.events = { ...state.events, ...payload }
      }
    }
    

    不要忘记改进您的 mapEventGetters 函数,因为数据不再嵌套在“数据”属性中。

    PS:但我个人不明白为什么不将 Vuex 与特定的 getter 一起使用,因为它有效,它会触发原始类型的反应:

    store/index.js

    import Vue from 'vue'
    import Vuex from 'vuex'
    
    const state = {
      events: {}
    }
    
    const actions = {
      publish({commit}, payload) {
        commit('publish_event', payload)
      }
    }
    
    const mutations = {
      publish_event(state, payload) {
        state.events = { ...state.events, ...payload }
      }
    }
    
    const getters = {
      fooBar: state => state.events.fooBar || ''
    }
    
    Vue.use(Vuex)
    
    export default new Vuex.Store({
      state,
      actions,
      mutations,
      getters
    })
    

    main.js

    import Vue from 'vue'
    import App from '@/App'
    import store from '@/store'
    
    new Vue({
      store,
      render: h => h(App)
    }).$mount('main')
    

    一些组件

    <template>
      <span>{{ fooBar }}</span>
    </template>
    
    import { mapGetters, mapActions } from 'vuex'
    
    export default {
      name: 'SomeComponent',
    
      computed: {
        ...mapGetters(['fooBar'])
      },
    
      methods: {
        ...mapActions(['publish'])
      },
    
      created () {
        setTimeout(() => {
          publish({
            fooBar: 123
          })
        }, 3000)
      }
    }
    

    【讨论】:

    • 谢谢。 “但我个人不明白为什么不将 Vuex 与特定的 getter 一起使用,因为它有效,它触发了原始类型的反应性”。如果两次发布之间的值保持不变,则不会
    • 然后呢?区别在哪里?您的突变也会重写特定键的数据。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-20
    • 2020-03-29
    • 1970-01-01
    • 2016-10-30
    • 1970-01-01
    相关资源
    最近更新 更多