web-dev-qa-db-ja.com

socket.ioを使用したReact-ReduxおよびWebsocket

私はそのテクノロジーReact-Reduxに慣れていないので、実装に協力してください。

ソケット(socket.io)を使用して1つのチャットアプリケーションを実装します。まず、ユーザーはサインアップする必要があります(サーバー側でパスポートを使用します)。その後、サインアップが成功した場合、ユーザーはwebSocketに接続する必要があります。

すべてのアクションにパイプのようなミドルウェアを使用し、ミドルウェアを取得するアクションの種類に応じて、さまざまなことを行うのが最善だと思いました。

アクションタイプがAUTH_USERの場合、クライアントサーバー接続を作成し、サーバーから送信されるすべてのイベントを設定します。

アクションタイプがMESSAGEの場合、サーバーにメッセージを送信します。

コードスニペット:

----- socketMiddleware.js ----

import { AUTH_USER,  MESSAGE } from '../actions/types';

import * as actions from 'actions/socket-actions';

import io from 'socket.io-client';

const socket = null;

export default function ({ dispatch }) {

    return next => action => {

        if(action.type == AUTH_USER) {

            socket = io.connect(`${location.Host}`);

            socket.on('message', data => {

               store.dispatch(actions.addResponse(action.data));

            });

        }

        else if(action.type == MESSAGE && socket) {

            socket.emit('user-message', action.data);

            return next(action)

        } else {
            return next(action)
        }
    }

}

------ index.js -------

import {createStore, applyMiddleware} from 'redux';

import socketMiddleware from './socketMiddleware';



const createStoreWithMiddleware = applyMiddleware(

  socketMiddleware

)(createStore);

const store = createStoreWithMiddleware(reducer);

<Provider store={store}>

    <App />

</Provider>

その実践についてどう思いますか、それはより良い実装ですか?

21
Ganbel

Spoiler:現在、私はオープンソースのチャットアプリケーションになるものを開発しています。

これを行うには、アクションをミドルウェアから、さらにはソケットクライアントをミドルウェアから分離することにより、より効果的に実行できます。したがって、次のような結果になります。

  • Types->すべてのリクエストのREQUEST、SUCCESS、FAILUREタイプ(必須ではありません)。
  • Reducer->異なる状態を保存する
  • アクション->接続/切断/放出/リッスンするアクションを送信します。
  • ミドルウェア->アクションを処理し、現在のアクションをソケットクライアントに渡すかどうか
  • Client->ソケットクライアント(socket.io)。

以下のコードは開発中の実際のアプリから取られたものであり(ほとんど編集されていない場合があります) .

アクション

アクションは可能な限り単純である必要があります。これは、アクションが繰り返し行われることが多く、おそらく多くのアクションが発生するためです。

export function send(chatId, content) {
  const message = { chatId, content };
  return {
    type: 'socket',
    types: [SEND, SEND_SUCCESS, SEND_FAIL],
    promise: (socket) => socket.emit('SendMessage', message),
  }
}

ソケットはパラメータ化された関数であることに注意してください。これにより、アプリケーション全体で同じソケットインスタンスを共有でき、インポートについて心配する必要はありません(これを行う方法は後で説明します)。

ミドルウェア(socketMiddleware.js):

erikras/react-redux-universal-hot-example が使用するものと同様の戦略を使用しますが、AJAXの代わりにソケットを使用します。

ソケットミドルウェアは、ソケットリクエストのみを処理します。

ミドルウェアはアクションをソケットクライアントに渡し、以下をディスパッチします。

  • REQUEST(アクションtypes[0]):要求しています(action.typeがリデューサーに送信されます)。
  • SUCCESS(アクションtypes[1]):リクエストの成功時(action.typeおよびサーバー応答はaction.resultとしてレデューサーに送信されます)。
  • FAILURE(アクションtypes[2]):要求が失敗したとき(action.typeおよびaction.errorとしてのサーバー応答がリデューサーに送信されます)。
export default function socketMiddleware(socket) {
  // Socket param is the client. We'll show how to set this up later.
  return ({dispatch, getState}) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState);
    }

    /*
     * Socket middleware usage.
     * promise: (socket) => socket.emit('MESSAGE', 'hello world!')
     * type: always 'socket'
     * types: [REQUEST, SUCCESS, FAILURE]
     */
    const { promise, type, types, ...rest } = action;

    if (type !== 'socket' || !promise) {
      // Move on! Not a socket request or a badly formed one.
      return next(action);
    }

    const [REQUEST, SUCCESS, FAILURE] = types;
    next({...rest, type: REQUEST});

    return promise(socket)
      .then((result) => {
        return next({...rest, result, type: SUCCESS });
      })
      .catch((error) => {
        return next({...rest, error, type: FAILURE });
      })
  };
}

SocketClient.js

Socket.io-clientをロードおよび管理する唯一のもの。

[optional](以下のコードの1を参照)。socket.ioに関する非常に興味深い機能の1つは、 message acknowledgements 、これはHTTPリクエストを行う際の典型的な応答です。これらを使用して、各リクエストが正しいことを確認できます。この機能を利用するには、サーバーのsocket.ioコマンドにもこの最新の確認パラメーターが必要です。

import io from 'socket.io-client';

// Example conf. You can move this to your config file.
const Host = 'http://localhost:3000';
const socketPath = '/api/socket.io';

export default class socketAPI {
  socket;

  connect() {
    this.socket = io.connect(Host, { path: socketPath });
    return new Promise((resolve, reject) => {
      this.socket.on('connect', () => resolve());
      this.socket.on('connect_error', (error) => reject(error));
    });
  }

  disconnect() {
    return new Promise((resolve) => {
      this.socket.disconnect(() => {
        this.socket = null;
        resolve();
      });
    });
  }

  emit(event, data) {
    return new Promise((resolve, reject) => {
      if (!this.socket) return reject('No socket connection.');

      return this.socket.emit(event, data, (response) => {
        // Response is the optional callback that you can use with socket.io in every request. See 1 above.
        if (response.error) {
          console.error(response.error);
          return reject(response.error);
        }

        return resolve();
      });
    });
  }

  on(event, fun) {
    // No promise is needed here, but we're expecting one in the middleware.
    return new Promise((resolve, reject) => {
      if (!this.socket) return reject('No socket connection.');

      this.socket.on(event, fun);
      resolve();
    });
  }
}

app.js

アプリの起動時に、SocketClientを初期化し、ストア構成に渡します。

const socketClient = new SocketClient();
const store = configureStore(initialState, socketClient, apiClient);

configureStore.js

socketMiddlewareを新しく初期化されたSocketClientとともにストアミドルウェアに追加します(後で説明することを伝えたパラメーターを覚えていますか?)。

export default function configureStore(initialState, socketClient, apiClient) {
const loggerMiddleware = createLogger();
const middleware = [
  ...
  socketMiddleware(socketClient),
  ...
];

[特別なことはない]アクションタイプの定数

特別なことはありません=通常は何をしますか。

const SEND = 'redux/message/SEND';
const SEND_SUCCESS = 'redux/message/SEND_SUCCESS';
const SEND_FAIL = 'redux/message/SEND_FAIL';

[特別なことはない] Reducer

export default function reducer(state = {}, action = {}) {
  switch(action.type) {
    case SEND: {
      return {
        ...state,
        isSending: true,
      };
    }
    default: {
      return state;
    }
  }
}

それは多くの作業のように見えるかもしれませんが、一度設定したらそれは価値があります。関連するコードは読みやすく、デバッグしやすくなり、間違いを犯しにくくなります。

PS:AJAX API呼び出しも同様にこの戦略に従うことができます。

32
zurfyx