如何在 redux 中存储客户端?

How to store client in redux?

我正在设置一个需要创建客户端的 redux 应用程序。初始化后,客户端有侦听器和 API 需要根据某些操作调用它们。

因此,我需要保留一个客户端实例。现在,我正在将其保存在该州。是吗?

所以我有以下 redux 动作创建者,但是当我想发送消息时,我需要调用 client.say(...) API。

但是我应该从哪里得到客户端对象呢?我应该从状态中检索客户端对象吗?我的理解是这是一个 redux 反模式。使用 redux 执行此操作的正确方法是什么?

更奇怪的是——当消息发送实际上并没有改变状态时,它应该被认为是动作创建者吗?

操作:

// actions.js

import irc from 'irc';

export const CLIENT_INITIALIZE = 'CLIENT_INITIALIZE';
export const CLIENT_MESSAGE_RECEIVED = 'CLIENT_MESSAGE_RECEIVED';
export const CLIENT_MESSAGE_SEND = 'CLIENT_MESSAGE_SEND';

export function messageReceived(from, to, body) {
  return {
    type: CLIENT_MESSAGE_RECEIVED,
    from: from,
    to: to,
    body: body,
  };
};

export function clientSendMessage(to, body) {
  client.say(...); // <--- where to get client from?
  return {
    type: CLIENT_MESSAGE_SEND,
    to: to,
    body: body,
  };
};

export function clientInitialize() {
  return (dispatch) => {
    const client = new irc.Client('chat.freenode.net', 'react');

    dispatch({
      type: CLIENT_INITIALIZE,
      client: client,
    });

    client.addListener('message', (from, to, body) => {
      console.log(body);
      dispatch(messageReceived(from, to, body));
    });
  };
};

这里是减速器:

// reducer.js
import { CLIENT_MESSAGE_RECEIVED, CLIENT_INITIALIZE } from '../actions/client';
import irc from 'irc';

export default function client(state: Object = { client: null, channels: {} }, action: Object) {
  switch (action.type) {
    case CLIENT_MESSAGE_RECEIVED:
      return {
        ...state,
        channels: {
          ...state.channels,
          [action.to]: [
            // an array of messages
            ...state.channels[action.to],
            // append new message
            {
              to: action.to,
              from: action.from,
              body: action.body,
            }
          ]
        }
      };

    case CLIENT_JOIN_CHANNEL:
      return {
        ...state,
        channels: {
          ...state.channels,
          [action.channel]: [],
        }
      };

    case CLIENT_INITIALIZE:
      return {
        ...state,
        client: action.client,
      };
    default:
      return state;
  }
}

使用中间件将客户端对象注入动作创建器! :)

export default function clientMiddleware(client) {
  return ({ dispatch, getState }) => {
    return next => (action) => {
      if (typeof action === 'function') {
        return action(dispatch, getState);
      }

      const { promise, ...rest } = action;
      if (!promise) {
        return next(action);
      }

      next({ ...rest });

      const actionPromise = promise(client);
      actionPromise.then(
        result => next({ ...rest, result }),
        error => next({ ...rest, error }),
      ).catch((error) => {
        console.error('MIDDLEWARE ERROR:', error);
        next({ ...rest, error });
      });

      return actionPromise;
    };
  };
}

然后应用它:

const client = new MyClient();

const store = createStore(
  combineReducers({
    ...
  }),
  applyMiddleware(clientMiddleware(client))
);

然后你就可以在action creators中使用它了:

export function actionCreator() {
  return {
    promise: client => {
      return client.doSomethingPromisey();
    }
  };
}

这主要改编自react-redux-universal-hot-example boilerplate project. I removed the abstraction that lets you define start, success and fail actions, which is used to create this abstraction in action creators

如果您的客户端不是异步的,您可以调整此代码以简单地传入客户端,类似于 redux-thunk 传入 dispatch 的方式。