Перед чтением этого рассказа настоятельно рекомендуется взглянуть на Часть 2, чтобы понять, как работает серверная часть. Введение и общее описание представлены в Части 1.

Структура папок клиентского приложения представлена ​​в репозитории в папке «client».



KilroggD / Reflux-ws
Reflux-ws - использование React и Reflux в приложениях реального времени на основе веб-сокетов github.com



Для обслуживания ресурсов и запуска сервера разработки мы используем webpack с поддержкой React и ES6. Вы можете найти пример настройки здесь

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

class List extends Reflux.Component {
 
  constructor(props) {
    super(props);
  }
 
  componentDidMount() {
    let ids = [];
    this.props.items.forEach((item) => {
      ids.push(item.id); 
    });
    Reflux.initStore(CounterStore);
    CounterActions.init(ids);
 }
 
  render() {
    return (
      <div className=”list”>
        {this.props.items.map((item, index) => {
          item.key = index;
          return <Item {…item} />
        })}
      </div>
    );
  }
 
};

После монтирования этого компонента он создает экземпляр CounterStore как синглтон и вызывает действие init, определенное в CounterActions.

const CounterActions = Reflux.createActions([
  ‘init’, ‘destroy’, ‘enable’, ‘disable’]);

Это действие запускает прослушиватель onInit в магазине, который создает соединение WebSocket.

onInit(ids) {
  this.ids = ids;
  if (!this.socket) {
    this.socket = io.connect(this.url);
  }
  this.handleConnect()
}

Хранилище содержит 3 основных обработчика для взаимодействия с WebSockets: handleConnect подписывается на обновления WebSocket с определенными идентификаторами и начинает прослушивать обновления. Следующий метод - handleUpdate - сохраняет пакеты обновлений в состоянии магазина. Метод handleDisconnect реагирует на отключение WebSocket со стороны сервера.

handleConnect() {
  if (!this.socket) {
    return false;
  }
  this.socket.on(‘connect’, () => {
    this.connected = true;
    let ids = this.ids;
    this.socket.emit(‘subscribe’, {ids}, () => {
      this.handleUpdate();
    });
    this.handleDisconnect();
  });
 }
handleUpdate() {
  if (!this.socket) {
    return false;
  }
  this.socket.on(‘update’, (data) => {
    this.setState({[data.id]: data});
  });
  this.socket.on(‘status’, (data) => {
    this.setState({[data.id]: data});
  });
  this.socket.on(‘error’, (data) => {
    alert(data.message);
    this.onDestroy();
  });
 }
handleDisconnect() {
  this.socket.on(‘disconnect’, () => {
    this.connected = false;
    this.setState({});
    return;
  });
 }

Также в магазине есть два действия для отправки сообщений на сервер через веб-сокеты для включения / отключения определенного элемента.

 onEnable(id) {
   this.socket.emit(‘enable’, {id});
 }
 
 onDisable(id) {
   this.socket.emit(‘disable’, {id});
 }

И еще одно действие для разрушения соединения WebSocket.

 onDestroy() {
   this.ids = [];
   this.socket.disconnect();
 }

Компонент счетчика отображается в каждом элементе списка List. Этот компонент прослушивает обновления с помощью функции Reflux mapStoreToState. По сути, эта функция принимает в качестве аргумента каждое изменение состояния магазина. Код внутри этой функции обеспечивает своего рода фильтр для сообщений об обновлении. Он принимает только пакет, связанный с этим идентификатором счетчика, и обрабатывает данные для обновления состояния компонента в соответствии с сообщением WebSocket, переданным из состояния хранилища.

class Counter extends Reflux.Component {
  constructor(props) {
    super(props);
    this.state = {
      number: 0,
      status: ‘enabled’,
    };
    this.processData = this.processData.bind(this);
    this.handleClick = this.handleClick.bind(this);
    this.mapStoreToState(CounterStore, (data) => {
      if (typeof data[this.props.id] !== ‘undefined’) {
        return this.processData(data[this.props.id]);
      } 
    });
 }
  processData(data) {
    let stateObj = Object.assign({}, this.state);
    if (typeof data.error !== ‘undefined’) {
      alert(data.error);
    }
    if (typeof data.number !== ‘undefined’
        && this.state.status !== ‘disabled’) {
      stateObj.number = data.number;
    }
    if (typeof data.status !== ‘undefined’) {
      stateObj.status = data.status;
    }
    return stateObj;
  }
  handleClick(e) { //change status on click
    if (this.state.status === ‘enabled’) {
      CounterActions.disable(this.props.id);
    }
    if (this.state.status === ‘disabled’) {
      CounterActions.enable(this.props.id);
    }
  }
  render() {
    const counterClass = ‘list__counter’ + ‘ ‘
      + ‘list__counter — ‘ + this.state.status;
    return (
      <div onClick={this.handleClick} className={counterClass}>
        {this.state.number}
      </div>
    );
  }
};

При событии «click» метод handleClick запускает действие enable или disable, которое вызывает связанный прослушиватель в магазине.

Чтобы протестировать это приложение, вы должны вытащить репозиторий, запустить серверное приложение, описанное в части 2 этой истории, затем перейти в папку клиента, при необходимости выполнить npm install, а затем вы можете запустить сервер разработки с помощью команды npm start в командной строке. Приложение должно быть доступно по адресу http: // localhost: 8080 /.