简体   繁体   中英

How to store client in redux?

I'm setting up a redux application that needs to create a client. After initialization, the client has listeners and and APIs that will need to be called based on certain actions.

Because of that I need to keep an instance of the client around. Right now, I'm saving that in the state. Is that right?

So I have the following redux action creators, but then when I want to send a message I need to call the client.say(...) API.

But where should I get the client object from? Should I retrieve the client object from the state? My understanding is that that's a redux anti-pattern. What's the proper way to do this with redux?

Even stranger – should the message send be considered an action creator when it doesn't actually mutate the state?

The actions:

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

And here is the reducer:

// 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;
  }
}

Use middleware to inject the client object into action creators! :)

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

Then apply it:

const client = new MyClient();

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

Then you can use it in action creators:

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

This is mostly adapted from the 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 .

If your client is not asynchronous, you can adapt this code to simply pass in the client, similar to how redux-thunk passes in dispatch .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM