Всем привет! 😄

Это моя первая статья на Medium, и я хотел рассказать об использовании socket.io в приложении Vue.

Проблема

Я хотел найти способ централизовать все события socket.io, полностью абстрагированные от логики приложения, чтобы мы могли отправлять события из одного модуля и прослушивать их в представлениях и компонентах приложения.

Решение

Для этого мы собираемся использовать шину событий для отправки событий при отправке событий socket.io. Я нашел 5-минутную статью для чтения, объясняющую концепцию шины событий, и я нашел ее действительно интересной и с большим потенциалом.

Сервер

Давайте проверим серверное приложение и скажем, что у нас есть следующая конфигурация и события socket.io:

import sio from 'socket.io'
import { Room } from './models/rooms'
import { Message } from './models/chat'
const io = sio(6226)
io.sockets.on('connection', function (socket) {
  console.log('Socket connected')
  socket.emit('new-user-connected')
  socket.on('subscribe', function(rooms) {
     console.log('Subscribing to rooms: ', rooms)
     rooms.forEach(room => socket.join(room))
  })
  socket.on('unsubscribe', function(room) {
      console.log('Leaving room: ', room)
      socket.leave(room)
  })
  socket.on('ack-user-connected', async function (user) {
    console.log('[ACK] - User-connected', user)
    const rooms = await Room.findByUser(user)
    console.log('Rooms', rooms)
    socket.emit('user-rooms', rooms)
  })
  socket.on('post-message', async function (message) {
    console.log('New message posted', message)
    // ...
    io.sockets.in(message.room).emit('new-message', message)
  })
})

Ход событий следующий:

  • Клиент подключается к socket.io, отправляя connectionevent
  • Когда пользователь аутентифицируется в клиенте, выдает ack-user-connected
  • Сервер выдает user-rooms с комнатами, к которым у пользователя есть доступ.
  • Затем в приложении Vue пользователь создает событие subscribe для подписки на все комнаты socket.io.
  • Как только пользователь присоединился ко всем комнатам, он может выдать событие post-messageevent.

Приложение Vue

У нас есть следующая структура проекта Vue:

src/
├── assets/
│   └── ...
├── components/
│   └── ...
├── views/
│   └── ...
├── services/
│   └── ...
│
├── App.vue
├── EventBus.js
├── io.js
├── main.js
├── router.js
├── store.js
└── vue.js
└── ...

Модуль EventBus.js просто экспортирует новый экземпляр Vue:

// EventBus.js
import Vue from 'vue'
export default new Vue()

Затем в модуле io.js делаем волшебство:

// io.js
import sio from 'socket.io-client'
import bus from './EventBus'
const io = sio('http://localhost:6226')
// Server-emitted events
io.on('user-rooms', function(rooms) {
  console.log('Received user rooms', rooms)
  bus.$emit('user-rooms', rooms)
})
io.on('new-message', function(message) {
  console.log('Received a new message: ', message)
  bus.$emit('new-message', message)
})
io.on('new-user-connected', function() {
  console.log('Connection established')
  bus.$emit('connection')
})
// Client-emitted events
bus.$on('user-connected', function(user) {
  console.log('Request user connection')
  io.emit('ack-user-connected', user)
})
bus.$on('post-message', function(message) {
  console.log('New message posted', message)
  io.emit('post-message', message)
})
bus.$on('subscribe', function(room) {
  console.log('Subscribe to room: ', room)
  io.emit('subscribe', room)
})

В этом модуле мы прослушиваем события сервера, и когда событие отправляется, мы немедленно отправляем соответствующее событие в EventBus, чтобы оно было доступно из любой точки нашего приложения Vue. Кроме того, каждый раз, когда отправляется событие шины, мы отправляем соответствующее событие socket.io на сервер.

Чтобы прослушивать события шины, нам нужно импортировать EventBus в любое представление или компонент, а затем создать прослушиватели в функции beforeMount(). Например:

<template>
  <!-- ... -->
</template>
<script>
import bus from '../EventBus'
export default {
  //...
  beforeMount() {
    // ASAP, we emit the user-connected event...
    bus.$emit('user-connected', this.user)
    // ...and listen to bus events
    bus.$on('user-rooms', (rooms) => {
      bus.$emit('subscribe', rooms.map(room => room._id))
      rooms.forEach(room => this.messages[room._id] = [])
    })
    bus.$on('new-message', (message) => {
      this.addMessage(message)
    })
  },
  beforeDestroy() {
    // We need to clear event listeners
    bus.$off('new-message')
    bus.$off('user-rooms')
  },
  methods: {
    sendMessage() {
      const newMessage = {
         text: this.message,
         user: this.user,
         room: this.currentRoom
      }
      bus.$emit('post-message', newMessage)
      this.message = ''
  },
  // ...
</script>

И это все! Теперь мы можем прослушивать события socket.io буквально из любого места в нашем приложении Vue.

Надеюсь, это поможет 😄