Перед чтением этого рассказа настоятельно рекомендуется взглянуть на Часть 2, чтобы понять, как работает серверная часть. Введение и общее описание представлены в Части 1.
Структура папок клиентского приложения представлена в репозитории в папке «client».
Для обслуживания ресурсов и запуска сервера разработки мы используем 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 /.