将 SignalR 与 Vue.js 和 Vuex 集成

Integrate SignalR with Vue.js and Vuex

我们目前正在开发一个基于 vue-cli webpack 模板的投票应用程序。由于我们希望以一致且可维护的方式存储和操作我们的投票状态,因此我们打算使用 vuex 进行状态管理。前后端的交互是基于websockets的,我们想用signalr,因为在之前的项目中已经证明它非常好。由于我们是 vue.js 的新手,我们需要一些如何将 signalr、vuex 和 vue.js 完美集成在一起的建议。

让我们描述一下场景:

前端从我们的后端获取一个事件以识别投票活动处于活动状态并且可以接收选定的答案。一段时间后,我们通知前端结果可用并将其显示给用户。在某些情况下,我们可能会进行另一次投票。重要的是我们能够断开连接以防文档被隐藏(页面可见性 api)。

我们的解决方法:

总的来说,我想为此目的实施一个特殊的 signal.service。该服务负责建立连接以及通过 websockets 发送和接收消息。由于我们无法从通用模块对 vuex 存储执行任何更改,因此我们认为 vuex 插件会比较合适。 vuex 插件应该包装 signalr.

如果我们收到 VotingStartEvent,我们将解锁相应的问题并将其显示给用户。如果用户回答了那个问题,我们将这个问题(已回答)的新状态提交给 vuex 存储。在我们的插件中,我们有一个对突变的订阅,我们将使用这个订阅将我们的投票发送到后端。以下片段说明了这个想法:

var plugin = (store) => {
  // ...
  store.subscribe((mutation, state) => {
    console.log('inside subscription');
    // if vote has answered state, call connection.send() to submit the message to the backend
  });

  connection.received((message) => {
    // ...
    var event = parseEvent(message);
    if (event.type === START) {
      store.commit({type: 'voting', item: 'unlocked'});
    }
    // ...
  });
}

这种方法好还是您认为有改进的余地?

不需要插件,也不需要像 Vue.prototype.$pusher = new Pusher('apiKey') 这样的结构。我持有 Pusher 实例,就像我需要在组件之间共享的任何其他值一样。我在 Vue 实例方法 create 中初始化 Pusher,就像我需要首先初始化的任何其他库一样。为澄清起见,我特意将交易数据保存在组件实例本身中,因为它们对于每个实例都是唯一的。不是所有你需要的,你必须只放置存储。

var store = new Vuex.Store({
  state: {
    pusher: null
  },
  mutations: {
    saveInstance(state, instance) {
      state.pusher = instance
    }
  },
  actions: {
    initializePusher ({commit}, apiKey) {
      commit('saveInstance', new Pusher(apiKey))
    }
  }
})

Vue.component('live-price', {
  template: '#live-price',
  props: ['pair'],
  data () {
    return {
      price: 'wait for next trade...',
      channel: null
    }
  },
  created () {
    this.channel = this.$store.state.pusher.subscribe('live_trades_' + this.pair)
    this.channel.bind('trade', data => this.price = data.price)
  }
})

new Vue({
  el: '#app',
  store,
  created () {
    this.$store.dispatch('initializePusher', 'de504dc5763aeef9ff52')
  }
})
[v-cloak] { display: none }
<div id="app">
  <live-price pair="btceur">BITCOIN price in EUR:</live-price>
  <live-price pair="ltceur">LITECOIN price in EUR:</live-price>
  <live-price pair="etheur">ETHEREUM price in EUR:</live-price>
  <live-price pair="xrpeur">RIPPLE price in EUR:</live-price>
</div>

<template id="live-price">
  <p>
    <slot></slot>
    <span v-cloak>{{ price }}</span>
  </p>
</template>

<script src="https://unpkg.com/vue@2.5.3/dist/vue.min.js"></script>
<script src="https://unpkg.com/vuex@3.0.1/dist/vuex.min.js"></script>
<script src="https://js.pusher.com/4.1/pusher.min.js"></script>