[Из песочницы] Быстрый курс Redux + websockets для бэкендера21.12.2016 18:34
Это краткое руководство и обучение по фронтэнеду для бэкендера. В данном руководстве я решаю проблему быстрого построения пользовательского интерфейса к серверному приложению в виде одностраничного веб-приложения (single page app).
Основной целью моего исследования является возможность за разумное время (для одного нормального человека) получить удобный и простой в использовании интерфейс-черновик к серверному приложению. Мы (как разработчики серверной части) понимаем, что наш приоритет — серверная часть. Когда (в гипотетическом проекте) появятся во фронте профи своего дела, они все сделают красиво и «правильно».
В роли учебной задачи представлена страничка чата с каким-то умозрительным «ботом», который работает на стороне сервера и принимает сообщение только через WebSocket. Бот при этом выполняет эхо ваших сообщений (мы тут не рассматриваем серверную часть вообще).
Мне для изложения материала требуется, чтобы вы имели:
базовое знание javascript (тут нужно поискать в интернете справочник по крайней версии js стандартов ES-2015)
знание reactjs (на уровне обучения https://facebook.github.io/react/tutorial/tutorial.html)
понятие о websockets (это очень просто, главное чтобы ваш сервер это умел)
знание и умение использовать bootstrap (на уровне этого раздела http://getbootstrap.com/css/)
Что используем
Redux — официальная документация расположена по адресу http://redux.js.org. По-русски есть несколько вариантов, я лично использовал в основном https://rajdee.gitbooks.io/redux-in-russian/content/docs/introduction/index.html.
Статью exec64, она стала причиной написать этот тутриал https://exec64.co.uk/blog/websockets_with_redux/.
Готовый сервер с react и redux от https://github.com/erikras/react-redux-universal-hot-example (он нам спасает человеко-месяцы времени по настройке большой связки технологий, которые необходимы для современного js проекта)
Мотивация
Вообще я разрабатываю приложение на языке Python. Погоди-погоди уходить …
Что мне было нужно:
мне нужно чтобы реализация интерфейса не диктовала мне выбор технологий на стороне серверной части
современные технологии (мне нечего было терять или быть чем-то обязанным «старым проверенным приемам»)
это должно быть одностраничное приложений (я уже сам выберу, где можно обновлять страницу целиком)
мне нужна реакция пользовательского интерфейса в реальном времени на серверные события
мне нужен обмен информацией сервер-клиент (а не клиент-сервер) в реальном времени
мне нужна возможность генерировать обновления клиента на сервере
Что было испробовано:
вариации на тему на чистом js (устарело, есть много полезных моделей велосипеда)
JQuery (уже не могу ТАК извратить так свой мозг, крайне сложный для быстрого старта синтаксис и… это дело профессионалов)
Angular (переход на 2 версию спугнул и не нашел за отведенное время лазейки к решению моей задачи)
Socket.io (там все реализовано, если вы node.js программист вы уже его используете, но он слишком сильно привязывает серверную часть на node, мне нужен только клиент без третьих лиц)
Выбрано в итоге:
React (понятно и доступно/просто + babel = делает язык вполне понятным)
Redux (импонирует использование единой помойки единого хранилища)
WebSockets (очень просто и не связывает руки, а позволяет внутри себя уже применять такой формат какой позволит фантазия)
Упрощения и допущения:
Мы не будем использовать авторизации в приложении
Мы не будет использовать авторизации в WebSocket-ах
Мы будем использовать самое доступное приложение Websocket Echo (https://www.websocket.org/echo.html)
Содержание
Часть первая. Первоначальная настройка. Настройка одной страницы
Часть вторая. Проектирование будущего приложения
Часть третья. Изумительный Redux
Часть четвертая. Оживляем историю
Часть пятая. Проектируем чат
Часть шестая. Мидлваре
Как читать
Не будете повторять — пропускайте часть 1
Знаете reactjs — пропускайте часть 2
Знаете redux — пропускайте части 3, 4 и 5
Знаете как работает middleware в redux — смело читайте часть 6 и далее в обратном порядке.
Часть первая. Первоначальная настройка. Настройка страницы.
Настройка окружения
Нам нужен node.js и npm.
Ставим node.js с сайта https://nodejs.org —, а именно этот гайд написан на 6ой версии, версию 7 тестировал — все работает.
npm устанавливается вместе с node.js
Далее нужно запустить npm и обновить node.js (для windows все тоже самое без npm)
sudo npm cache clean -f
sudo npm install -g n
sudo n stable
проверяем
node -v
Настройка react-redux-universal-hot-example
Все выложено в react-redux-universal-hot-example, там же инструкция по установке.
Тут привожу последовательность действий
Скачиваем и разархивируем архив/форкаем/что-угодно-как-вам-нравится.
Через node.js command line или терминал переходим в эту папку
Запускаем
npm install
npm run dev
Переходим на http://localhost:3000 и должны видеть стартовую страницу.
Если все ок — приступаем.
Создаем новый контейнер
Для настройки раздела используем предоставленную справку от команды react-redux-universal-hot-example. Оригинал статьи находится тут.
cd ./src/containers && mkdir ./SocketExample
Копируем туда hello.js как шаблон странички
cp About/About.js Hello/SocketExamplePage.js
Я использую для всего этого Atom, как действительно прекрасный редактор-чего-угодно с некоторыми плюшками.
Правим скопированный файл
Создаем заглушку под нашу страница. Вводим элемент . Позже будем выводить статус соединения в этот элемент.
import React, {Component} from 'react';
import Helmet from 'react-helmet';
export default class SocketExamplePage extends Component {
render() {
return (
Socket Exapmle Page
Sockets not connected
);
}
}
Подключаем созданную страницу
Добавляем в ./src/containers/index.js новый компонент React
export SocketExamplePage from './SocketExample/SocketExamplePage';
Добавляем в ./src/routes.js, чтобы связать переход по пунти /socketexamplepage в карту ссылок
Прежде чем начнем. Я все разрабатывал в обратном порядке — сначала крутил мидлваре, потом прокидывал экшены и только потом уже прикручивал адекватный интерфейс в reactjs. Мы в руководстве будем делать все в правильном порядке, потому что так действительно быстрее и проще. Минус моего подхода в том, что я использовал в разы больше отладки и «костылей», чем нужно на самом деле. Будем рациональными.
Часть вторая. Проектирование будущего приложения
Сначала мы проектируем интерфейс пользователя. Для этого мы примерно представляем, как должен выглядеть скелет нашего интерфейса и какие действия будут происходить в нем.
В руководстве для начинающих React представлен подход по проектированию динамических приложений на React, от которого мы не будем отклоняться, а прямо будем следовать по нему.
Дэн Абрамов писал в своей документации много про то, что и как нужно разделять в приложении и как организовывать структуру приложения. Мы будем следовать его примеру.
Итак начнем.
Прежде всего хочу сказать, что для наглядности и отладки прямо при написании приложения мы будем добавлять элементы уберем с формы после окончания работы.
Пользовательский интерфейс «Вариант 1»
Мы добавляем два новых раздела на нашу страницу.
В логе подключения сокетов будем кратко выводить текущие события, связанные с подключением отключением. Изменяем файл ./src/containers/SocketExample/SocketExamplePage.js.
// inside render () { return (...) }
Socket connection log
index — порядковый номер записи лога
loaded — признак загружен ли элемент на странице пользователя
message — переменна-сообщение для отладки и наглядности кода
connected — признак подключены ли мы сейчас к серверу
Конечно мы забыли про кнопки и поля ввода, добавляем:
подключиться к websocket
отключиться от websocket
В логе сообщений будем отображать отправленные -> и полученные сообщения <-.
// inside render () { return (...) }
Message log
Socket string
[ECHO] Socket string
Кнопка и ввод для отправить сообщение
Не нажимайте кнопку Send
Проверяем и закомитимся для получения полного кода.
Про экшены написано В официальной документации
Про редюсеры написано Там же
Создаем файл
Создаем файл ./src/redux/modules/socketexamplemodule.js и наполняем базовыми экшенами и редюсерами. Вот тут базовом примере есть странная нестыковка, все предлагается писать в одном файле, не разделяя на файл экшенов и редюсеров, ну допустим. Все равно — мы тут все взрослые люди (we are all adults).
Все экшены мы будем запускать по нажатию кнопок, кроме события SOCKETS_MESSAGE_RECEIVING, который мы будем синтетически вызывать вслед за отправкой сообщения. Это делается, чтобы в процессе разработки эмулировать недостающие в настоящий момент (или на конкретном этапе) функционал серверной части приложения.
Более подробно про структуру reducer и зачем Object.assign({}, state,{}); можно прочитать тут.
Вы заметили инициализацию state = initialState, которой мы не объявили (поставьте ESLint или его аналог — сильно упростит жизнь Нормального Человека). Добавим объявление до редюсера. Это будет первое состояние, которое мы будем иметь в нашем сторе на момент загрузки страницы, ну точнее страница будет загружаться уже с этим первоначальным состоянием.
А стор у нас уже создан и редюсер в него подключен. Ничего не делаем.
Если подробнее, то вы должны помнить, как мы добавили наш редюсер в combineReducers выше по статье. Так вот этот combineReducers сам включается в стор, который создаётся в файле ./src/redux/create.js.
Подключаем стор к react компонентам
Подключаем все это теперь в наши модули. В целях всесторонней демонстрации начнем с модуля истории и сделаем из него чистый компонент react (в смысле чистый от redux).
Компонент SocketConnectionLog мы пока не трогаем, а идем сразу в контейнер SocketExamplePage.
В данном контейнере мы будем подключать и получать данные из redux.
Подключаем библиотеку в файле ./src/containers/SocketExample/SocketExamplePage.js.
import {connect} from 'react-redux';
Забираем экшены, чтобы потом их использовать у себя в react.
import * as socketExampleActions from 'redux/modules/socketexamplemodule';
а еще мы поменяем строку, чтобы подключить PropTypes
import React, {Component, PropTypes} from 'react';
Пишем коннектор, которым будем забирать данные из нашего редюсера.
Мы получили данные теперь давайте их передавать. Внутри блока render объявляем и принимаем данные уже теперь из props.
const {loaded, message, connected} = this.props;
и спокойно и уверенно передаем их в наш модуль:
Мы передали новые данные (через react) в компонент. Теперь переписываем наш компонент, который уже ничего не знает про стор (redux), а только обрабатывает переданные ему данные.
В файле ./src/components/SocketExampleComponents/SocketConnectionLog.js действуем по списку:
проверяем полученные props
присваиваем их внутри render
используем в нашем компоненте
Начнем, импортируем недостающие библиотеки:
import React, {Component, PropTypes} from 'react';
Теперь переходим к компоненту SocketExampleMessageLog и сделаем его абсолютно самостоятельным, в смысле работы со стором. Мы не будем передавать в него никакие props, он будет получать все, что ему нужно из стор сам.
import React, {Component, PropTypes} from 'react';
import {connect} from 'react-redux';
import * as socketExampleActions from 'redux/modules/socketexamplemodule';
добавляем connect, проверку типов и используем полученные данные
В предыдущих частя мы все подготовили к тому, чтобы начать использовать стор.
В этой части мы будем связывать события в react и состояния в стор. Начнем.
Оживим историю подключений в нашем компоненте ./src/components/SocketExampleComponents/SocketConnectionLog.js.
Но как мы помним, он ничего про стор не знает. Это означает, что он ничего не знает про экшены и поэтому ему их нужно передать через контейнер ./src/containers/SocketExample/SocketExamplePage.js. Просто передаем компоненту их как будто это простые props.
Вообще все функции экшенов мы подключили через connect. Стоп. Подробней. Вспомним.
//....
import * as socketExampleActions from 'redux/modules/socketexamplemodule';
//....
@connect(
state => ({
loaded: state.socketexample.loaded,
message: state.socketexample.message,
connected: state.socketexample.connected}),
socketExampleActions)
Поэтому просто включаем их в проверку в файле ./src/containers/SocketExample/SocketExamplePage.js:
Теперь давайте обеспечим прием преданных в компонент экшенов в файле ./src/components/SocketExampleComponents/SocketConnectionLog.js.
Мы будем добавлять их (экшены) в проверку и использовать в наших обработчиках действий на форме. Обработчиков сделаем два: по клику кнопки «Connect» и «Disconnect».