【问题标题】:Architecture in a react native app using WebSockets使用 WebSockets 的反应原生应用程序中的架构
【发布时间】:2018-04-10 04:46:02
【问题描述】:

我要构建一个使用 WebSockets 的 React Native 应用程序。我有一个用 JavaScript 编写的 WebSocket 库,我只是在这个项目中重新使用它,这太棒了。

我的问题是,作为 React/React Native 的新手,设置和维护通过 WebSocket 的所有流量的最佳做法是什么?

最初我的想法是在主 App 组件中创建 websocket,如下所示:

export default class App extends Component {

  constructor(props) {
    super(props);
    this.ws = new WebSocket;
  }

  componentWillMount() {
    console.log(this.ws);
  }

  render() {
    console.log("We are rendering the App component.....");

    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>Hello, world</Text>  
      </View>
    );
  }
}

实际的 WebSocket 类将包含所有相应的连接处理:

ws.onopen = () => {
  // connection opened
  ws.send('something'); // send a message
};

ws.onmessage = (e) => {
  // a message was received
  console.log(e.data);
};

ws.onerror = (e) => {
  // an error occurred
  console.log(e.message);
};

ws.onclose = (e) => {
  // connection closed
  console.log(e.code, e.reason);
};

我的问题是,由于来自 WebSocket 的数据将适用于通过 React Native 应用程序中的许多组件的状态,但它不是一个将扩展 React.Component 的类,我是否不与 @987654324 中的 Redux 交互@ 班级?我是否将所有 WebSocket 连接处理移至 App 组件并将那里的操作分派给 Redux?

这里实例化我的 WebSocket 类并确保其中的所有流量正确传递到 Redux 以便所有组件的状态都能正确传递的常见模式是什么?

【问题讨论】:

    标签: javascript reactjs react-native redux


    【解决方案1】:

    到目前为止,这里的答案很好。只是想补充一点,您保存数据的哪里 应该是基于数据的类型 的决定。 James Nelson 有我经常提到的an excellent article on this topic

    对于你的情况,让我们谈谈前 3 种状态:

    1. 数据
    2. 通讯状态
    3. 控制状态

    数据

    您的 WebSocket 连接是通用的,从技术上讲可以返回任何内容,但您收到的消息很可能是数据。例如,假设您正在构建一个聊天应用程序。然后,所有已发送和接收的消息的日志将是数据。您应该使用messages reducer 将此数据存储在 redux 中:

    export default function messages(state = [], action) {
        switch (action.type) {
            case 'SEND_MESSAGE': 
            case 'RECEIVE_MESSAGE': {
                return [ ...state, action.message ];
            } 
    
            default: return state;
        }
    }
    

    我们不必(也不应该)在我们的 reducer 中包含任何 WebSocket 逻辑,因为它们是通用的并且不关心数据来自哪里。

    另外,请注意,这个 reducer 能够以完全相同的方式处理发送和接收。这是因为网络通信是由我们的通信状态 reducer 单独处理的。

    通信状态

    由于您使用的是 WebSockets,因此您要跟踪的通信状态类型可能与我的示例不同。在使用标准 API 的应用中,我会跟踪请求何时加载失败成功

    在我们的聊天应用示例中,您可能希望在发送消息时跟踪这些请求状态,但您也可能希望将其他事物归类为通信状态。

    我们的network reducer 可以使用与messages reducer 相同的操作:

    export default function network(state = {}, action) {
        switch (action.type) {
            case 'SEND_MESSAGE': {
                // I'm using Id as a placeholder here. You'll want some way
                // to tie your requests with success/failure receipt.
                return { 
                    ...state, 
                    [action.id]: { loading: true }
                };
            } case 'SEND_MESSAGE_SUCCESS': {
                return { 
                    ...state, 
                    [action.id]: { loading: false, success: true }
                };
            } case 'SEND_MESSAGE_FAILURE': {
                return { 
                    ...state, 
                    [action.id]: { loading: false, success: false }
                };
            }
    
            default: return state;
        }
    }
    

    这样,我们可以很容易地找到我们的请求的状态,并且我们不必为我们的组件中的加载/成功/失败而烦恼。

    但是,由于您使用的是 WebSocket,因此您可能并不关心任何给定请求的成功/失败。在这种情况下,您的通信状态可能只是您的套接字是否已连接。如果这听起来更好,那么只需编写一个 connection reducer 来响应打开/关闭操作。

    控制状态

    我们还需要一些东西来启动消息的发送。在聊天应用示例中,这可能是一个提交按钮,用于发送输入字段中的任何文本。我不会演示整个组件,因为我们将使用controlled component

    这里的要点是控制状态是消息发送之前。在我们的例子中,有趣的代码是在handleSubmit 中做什么:

    class ChatForm extends Component {
        // ...
        handleSubmit() {
            this.props.sendMessage(this.state.message);
            // also clear the form input
        }
        // ...
    }
    
    const mapDispatchToProps = (dispatch) => ({
        // here, the `sendMessage` that we're dispatching comes
        // from our chat actions. We'll get to that next.
        sendMessage: (message) => dispatch(sendMessage(message))
    });
    
    export default connect(null, mapDispatchToProps)(ChatForm);
    

    所以,这解决了我们所有状态的何处。我们创建了一个通用应用程序,该应用程序可以使用操作调用 fetch 以获得标准 API、从数据库或任意数量的其他来源获取数据。在您的情况下,您想使用WebSockets。所以,这个逻辑应该存在于你的行动中。

    动作

    在这里,您将创建所有处理程序:onOpenonMessageonError 等。这些仍然可以相当通用,因为您已经单独设置了 WebSocket 实用程序。

    function onMessage(e) {
        return dispatch => {
            // you may want to use an action creator function
            // instead of creating the object inline here
            dispatch({
                type: 'RECEIVE_MESSAGE',
                message: e.data
            });
        };
    }
    

    我在这里使用thunk 进行异步操作。对于这个特定的示例,这可能不是必需的,但您可能会遇到这样的情况,您希望发送消息然后处理成功/失败并从单个 sendMessage 操作中将多个操作发送到您的减速器。 Thunk 非常适合这种情况。

    全部连接在一起

    最后,我们已经完成了所有设置。我们现在要做的就是初始化 WebSocket 并设置适当的侦听器。我喜欢 Vladimir 建议的模式——在构造函数中设置套接字——但我会参数化你的回调,以便你可以提交你的操作。然后你的 WebSocket 类就可以设置所有的监听器了。

    通过创建 WebSocket 类 a singleton,您可以从您的操作内部发送消息,而无需管理对活动套接字的引用。您还可以避免污染全局命名空间。

    通过使用单例设置,只要您第一次调用new WebSocket(),您的连接就会建立。因此,如果您需要在应用程序启动后立即打开连接,我会在AppcomponentDidMount 中设置它。如果惰性连接没问题,那么您可以等到您的组件尝试发送消息。该操作将创建一个新的 WebSocket 并建立连接。

    【讨论】:

      【解决方案2】:

      您可以为WebSocket 创建专用类并在任何地方使用它。这是简单、简洁和清晰的方法。此外,您将把所有与 websocket 相关的东西都封装在一个地方!如果你希望你甚至可以从这个类中创建单例,但总体思路是这样的:

      class WS {
        static init() {
          this.ws = new WebSocket('ws://localhost:5432/wss1');
        }
        static onMessage(handler) {
          this.ws.addEventListener('message', handler);
        }
        static sendMessage(message) {
          // You can have some transformers here.
          // Object to JSON or something else...
          this.ws.send(message);
        }
      }
      

      您只在index.jsapp.js 的某个地方运行了init:

      WS.init();
      

      现在您可以从任何应用层、任何组件、任何地方松散地发送消息:

      WS.sendMessage('My message into WebSocket.');
      

      并从 WebSocket 接收数据:

      WS.onMessage((data) => {
        console.log('GOT', data);
        // or something else or use redux
        dispatch({type: 'MyType', payload: data});
      });
      

      因此,您可以在任何地方使用它,即使在 redux 中的任何操作或其他地方!

      【讨论】:

        【解决方案3】:

        没有关于此的官方指南。我认为使用组件会让人感到困惑,因为它不会被渲染,而且我猜如果你使用 Redux,你想在应用程序的任何地方共享来自 websocket 的数据。

        您可以将调度功能提供给您的 Websocket 管理器。

        const store = createStore(reducer);
        
        const ws = new WebSocketManager(store.dispatch, store.getState);
        

        在你的类方法中使用this.dispatch

        // inside WebSocketManager class
        constructor(dispatch, getState) {
            this.dispatch = dispatch;
            this.getState = getState;
        }
        

        你也可以使用中间件来处理副作用,我认为这是推荐的方式。您可以查看两个很棒的库:

        redux-saga

        redux-observable

        【讨论】:

          猜你喜欢
          • 2018-05-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-04-03
          • 2016-08-23
          • 2020-07-18
          • 2022-08-19
          • 1970-01-01
          相关资源
          最近更新 更多