Подтвердить что ты не робот

Архитектура в ответном родном приложении с использованием WebSockets

У меня есть приложение React Native, которое я собираюсь создавать, используя WebSockets. У меня есть библиотека WebSocket, написанная на JavaScript, и я просто повторно использую ее для этого проекта, что является фантастическим.

Мой вопрос заключается в том, чтобы быть новым для React/React Native, что лучше всего подходит для настройки и поддержки всего трафика, проходящего через WebSocket?

Первоначально моя идея состояла в том, чтобы создать 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, не взаимодействовать ли я с Redux в WebSocket класс? Переместить всю обработку соединения с WebSocket на компонент App и отправить туда команды Redux?

Какой общий шаблон здесь для создания экземпляра моего класса WebSocket и убедитесь, что весь трафик в нем правильно передан в Redux, поэтому все состояние компонента будет правильно выполнено?

4b9b3361

Ответ 1

Отличные ответы здесь до сих пор. Просто хотел добавить, что, когда вы держите свои данные, действительно должно быть решение, основанное на том, какой тип данных он есть. Джеймс Нельсон отличная статья по этой теме, на которую я ссылаюсь регулярно.

В вашем случае расскажите о первых трех типах состояний:

  • Данные
  • Состояние связи
  • Состояние управления

Данные

Ваше подключение к WebSocket является общим и может технически возвращать что-либо, но, вероятно, сообщения, которые вы получаете, являются данными. Например, скажем, вы создаете приложение для чата. Тогда будут записываться все сообщения, которые были отправлены и получены. Эти данные следует хранить в редуксе с помощью редуктора messages:

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

        default: return state;
    }
}

Нам не нужно (и мы не должны) иметь какую-либо логику WebSocket в наших редукторах, поскольку они носят общий характер и не заботятся о том, откуда поступают данные.

Также обратите внимание, что этот редуктор способен обрабатывать отправку и прием точно так же. Это связано с тем, что сетевая связь обрабатывается отдельно нашим редуктором состояния связи.

Состояние связи

Поскольку вы используете WebSockets, типы состояния связи, которые вы хотите отслеживать, могут отличаться от моего примера. В приложении, использующем стандартный API, я бы отслеживал, когда запрос Загрузка, не удалось или успешно.

В нашем примере приложения для чата вы, вероятно, захотите отслеживать эти состояния запроса всякий раз, когда вы отправляете сообщение, но могут быть и другие вещи, которые вы хотите классифицировать как состояние связи.

Наш редуктор network может использовать те же действия, что и редуктор messages:

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

Таким образом, мы можем легко найти статус наших запросов, и нам не нужно беспокоиться о загрузке/успехе/сбое в наших компонентах.

Тем не менее, вам может не понравиться успех или неудача любого заданного запроса, так как вы используете WebSockets. В этом случае ваше состояние связи может быть просто связано с подключением вашего сокета. Если это звучит лучше для вас, просто напишите редуктор connection, который реагирует на действия при открытии/закрытии.

Состояние управления

Нам также потребуется инициировать отправку сообщений. В примере приложения чата это, вероятно, кнопка отправки, которая отправляет любой текст в поле ввода. Я не буду демонстрировать весь компонент, так как мы будем использовать контролируемый компонент.

Вывод здесь заключается в том, что состояние управления является сообщением перед его отправкой. Интересный бит кода в нашем случае - что делать в 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. Итак, эта логика должна жить в ваших действиях.

Действия

Здесь вы создадите все свои обработчики: onOpen, onMessage, onError и т.д. Они все равно могут быть довольно универсальными, так как у вас уже установлена ​​ваша утилита 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 и настроить соответствующих слушателей. Мне нравится шаблон, предложенный Владимиром - настройка сокета в конструкторе - но я бы параметризовал ваши обратные вызовы, чтобы вы могли выполнять свои действия. Тогда ваш класс WebSocket может настроить всех слушателей.

Сделав класс WebSocket singleton, вы сможете отправлять сообщения внутри своих действий, не требуя управления ссылками на активный сокет, Вы также избегаете загрязнения глобального пространства имен.

Используя однострочный набор, всякий раз, когда вы вызываете new WebSocket() в первый раз, ваше соединение будет установлено. Итак, если вам нужно открыть соединение, как только приложение запустится, я бы установил его в componentDidMount из App. Если ленивое соединение в порядке, то вы можете просто подождать, пока ваш компонент попытается отправить сообщение. Действие создаст новый WebSocket, и соединение будет установлено.

Ответ 2

Вы можете создать выделенный класс для 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);
  }
}

Вы только запускаете init где-то в index.js или app.js:

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

Таким образом, вы можете использовать его повсюду даже в редуксе в любом действии или в другом месте!

Ответ 3

Официальных рекомендаций по этому поводу нет. Я думаю, что использование компонента сбивает с толку, потому что оно не будет отображаться, и я предполагаю, что если вы используете Redux, вы хотите поделиться данными из websocket в любом месте приложения.

Вы можете передать диспетчеру свой менеджер Websocket.

const store = createStore(reducer);

const ws = new WebSocket(store.dispatch, store.getState);
//  constructor(dispatch, getState) {
//    this.dispatch = dispatch;
//    this.getState = getState;
//  }

И используйте this.dispatch внутри ваших методов класса.

Вы также можете использовать middlewares для обработки побочных эффектов, я думаю, что это рекомендуемый способ. Есть две большие библиотеки, которые вы можете посмотреть:

redux-saga

redux-observable