Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. React
Code

Как создать ленту в реальном времени с помощью Phoenix и React

by
Difficulty:IntermediateLength:LongLanguages:

Russian (Pусский) translation by Marat Amerov (you can also view the original English article)

Final product image
What You'll Be Creating

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

Введение

Elixir известен своей стабильностью и функциями реального времени, а Phoenix использует способность Erlang VM обрабатывать миллионы соединений наряду с красивым синтаксисом и производительным инструментарием Elixir. Это поможет нам в создании обновления данных в реальном времени через API, которые будут использоваться нашим приложением React для отображения данных в пользовательском интерфейсе.

Начало

У вас должны быть установлены Elixir, Erlang и Phoenix. Подробнее об этом можно узнать на веб-сайте Phoenix. Кроме того, мы будем использовать заготовку React boilerplate, поскольку она хорошо поддерживается и правильно документирована.

Подготовка API

В этом разделе мы загрузим наше приложение Phoenix API и добавим каналы для обновления API в режиме реального времени. Мы просто будем работать с фидом (он будет содержать заголовок и описание), и как только его значение будет изменено в базе данных, API отправит обновленное значение нашему фтонтенд приложению.

Создание приложения

Давайте сначала создадим приложение Phoenix.

mix phoenix.new realtime_feed_api --no-html --no-brunch

Это создаст скелет Phoenix приложения внутри папки с именем realtime_feed_api. Опция --no-html не будет создавать все статические файлы (что полезно, если вы создаете приложение только для API), а опция --no-brunch не будет включать сборщик статики Phoenix, Brunch. Убедитесь, что вы устанавливаете зависимости, когда это запрашивается.

Давайте зайдем в папку и создадим нашу базу данных.

cd realtime_feed_api

Нам нужно будет удалить поля username и password из нашего файла config/dev.exs, так как мы будем создавать нашу базу данных без какого-либо имени пользователя или пароля. Это для упрощения, чтобы все было просто для этого урока. Для вашего приложения, убедитесь, что вы  сначала создайте базу данных с именем пользователя и паролем.

mix ecto.create

Вышеупомянутая команда создаст нашу базу данных. Теперь мы можем запустить наш Phoenix-сервер и проверить, все ли в порядке.

mix phoenix.server

Вышеупомянутая команда запускает наш Phoenix-сервер, и мы можем перейти к http://localhost:4000, чтобы увидеть, как он работает. В настоящее время он будет бросать  ошибку no route found, поскольку мы еще не создали никаких маршрутов!

Не стесняйтесь проверять свои изменения с помощью моего коммита.

Добавление модели фида

На этом этапе мы добавим нашу модель Feed в наше приложение Phoenix. Модель Feeds будет состоять из title и description.

mix phoenix.gen.json Feed feeds title:string description:string

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

Вам нужно добавить маршрут /feeds в файл web/router.ex внутри скоупа api:

resources "/feeds", FeedController, except: [:new, :edit]

Нам также необходимо выполнить миграцию, чтобы создать таблицу feeds в нашей базе данных:

mix ecto.migrate

Теперь, если мы перейдем на http://localhost:4000/api/feeds, мы увидим, что API отправляет нам пустой ответ, поскольку в нашей таблице feeds нет данных.

Вы можете проверить мой коммит для справки.

Добавление канала фидов

На этом этапе мы добавим канал Feed в наше приложение Phoenix. Каналы предоставляют средства для двунаправленной связи от клиентов, которые интегрируются со слоем Phoenix.PubSub для легкой функциональности в реальном времени.

mix phoenix.gen.channel feed

Вышеприведенная команда создаст файл feed_channel.ex внутри папки web/channels. Через этот файл наше приложение React будет обмениваться обновленными данными с базой данных с помощью сокетов.

Нам нужно добавить новый канал в наш файл web/channels/user_socket.ex:

channel "feeds", RealtimeFeedApi.FeedChannel

Поскольку мы не делаем аутентификацию для этого приложения, мы можем изменить наш файл web/channels/feed_channel.ex. Нам понадобится один метод join для нашего приложения React, чтобы присоединиться к нашему каналу фидов, один метод handle_out, чтобы отправлять полезную нагрузку через соединение сокета, и один метод broadcast_create, который будет транслировать полезную нагрузку всякий раз, когда в базе данных создается новый фид.

Эти три метода определены выше. В методе broadcast_create мы используем app/FeedsPage/HAS_NEW_FEEDS, так как мы будем использовать это как константу для нашего контейнера состояний Redux, который будет отвечать за то, чтобы фронтенду было известно, что в базе данных есть новые фиды. Мы обсудим это, когда мы создадим наше фронтенд приложение.

В конце концов, нам нужно будет только вызвать метод broadcast_change через наш файл feed_controller.ex всякий раз, когда в наш метод create вставляются новые данные. Наш метод create будет выглядеть примерно так:

Метод create отвечает за вставку новых данных в базу данных. Вы можете проверить мой коммит для справки.

Добавление поддержки CORS для API

Нам нужно реализовать эту поддержку, поскольку в нашем случае API обслуживается с http://localhost:4000, но наше фронтенд приложение будет работать на http://localhost:3000. Добавление поддержки CORS очень просто. Нам просто нужно добавить cors_plug в наш файл mix.exs:

Теперь мы останавливаем наш сервер Phoenix с помощью Control-C и выбираем зависимость, используя следующую команду:

mix deps.get

Нам нужно добавить следующую строку в наш файл lib/realtime_feed_api/endpoint.ex:

plug CORSPlug

Вы можете проверить мой коммит. Мы выполнили все наши текущие изменения. Давайте теперь сосредоточимся на фронтенд приложении.

Обновление данных фронтенд в реальном времени

Как упоминалось ранее, мы будем использовать react-boilerplate, чтобы начать работу с нашим фронтенд приложением. Мы будем использовать саму Redux saga, который будет слушать наши отправленные действия, и на основе этого пользовательский интерфейс будет обновлять данные.

Поскольку в шаблоне уже настроено все, нам не нужно его настраивать. Тем не менее, мы будем использовать команды, доступные в шаблоне, чтобы подкрасить наше приложение. Давайте сначала клонируем репозиторий:

git clone https://github.com/react-boilerplate/react-boilerplate.git realtime_feed_ui

Создание приложения

Теперь нам нужно зайти в папку realtime_feed_ui и установить зависимости.

cd realtime_feed_ui && npm run setup

Это инициализирует новый проект с помощью этого шаблона, удаляет историю git react-boilerplate, устанавливает зависимости и инициализирует новый репозиторий.

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

npm run clean

Теперь мы можем запустить наше приложение, используя npm run start и посмотреть, как он работает на http://localhost:3000/.

Вы можете ссылаться на мой коммит.

Добавление необходимых контейнеров

На этом этапе мы добавим два новых контейнера FeedsPage и AddFeedPage в наше приложение. Контейнер FeedsPage отобразит список фидов, а контейнер AddFeedPage позволит нам добавить новый фид в нашу базу данных. Для создания наших контейнеров мы будем использовать react-boilerplate.

npm run generate container

Вышеупомянутая команда используется для скаффолдинга в нашем приложении. После того, как вы наберете эту команду, он попросит имя компонента, которое в нашем случае будет FeedsPage, и мы будем использовать параметр Component на следующем шаге. Нам не понадобятся заголовки, но нам понадобятся actions/constants/selectors/reducer, а также sages для наших асинхронных потоков. Нам не нужны i18n messages для нашего приложения. Нам также необходимо будет придерживаться аналогичного подхода для создания нашего контейнера AddFeedPage.

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

Давайте просто добавим наши контейнеры в наш файл routes.js:

Это добавит наш контейнер FeedsPage в наш маршрут /feeds. Мы можем проверить это, посетив http://localhost:3000/feeds. В настоящее время он будет полностью пустым, поскольку в наших контейнерах ничего нет, но в консоли нашего браузера ошибок не будет.

Мы сделаем то же самое для нашего контейнера AddFeedPage.

Вы можете ссылаться на мой коммит для всех изменений.

Создание листинговой страницы фидов

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

Начнем с добавления наших констант в наш файл app/container/FeedsPage/constants.js:

Нам понадобятся эти четыре константы:

  • Константа FETCH_FEEDS_REQUEST будет использоваться для инициализации запроса на выборку.
  • Константа FETCH_FEEDS_SUCCESS будет использоваться, когда запрос на выборку будет успешным.
  • Константа FETCH_FEEDS_ERROR будет использоваться, когда запрос на выборку не будет выполнен.
  • Константа HAS_NEW_FEEDS будет использоваться, если в нашей базе данных есть новый фид.

Давайте добавим наши действия в наш файл app/container/FeedsPage/actions.js:

Все эти действия не требуют объяснений. Теперь мы структурируем initialState нашего приложения и добавим редюсер в наш файл app/container/FeedsPage/reducer.js:

Это будет initialState нашего приложения (состояние перед загрузкой данных). Поскольку мы используем ImmutableJS, мы можем использовать его структуру данных List для хранения наших неизменных данных. Функция редюсера будет выглядеть примерно так:

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

Пришло время создавать наши селекторы с помощью reselect, который является библиотекой селекторов для Redux. С помощью reselect мы можем легко извлекать значения сложного состояния. Давайте добавим следующие селекторs в наш app/containers/FeedsPage/selectors.js:

Как вы можете видеть здесь, мы используем структуру нашего initialState для извлечения данных из нашего состояния. Вам просто нужно запомнить синтаксис reselect.

Пришло время добавить наши sagas с использованием redux-sage. Здесь основная идея состоит в том, что нам нужно создать функцию для извлечения данных и другую функцию, чтобы наблюдать за начальной функцией, так что всякий раз, когда отправляется какое-либо конкретное действие, нам нужно вызвать начальную функцию. Давайте добавим функцию, которая будет извлекать наш список фидов из бекенд приложения в нашем файле app/container/FeedsPage/sagas.js:

Здесь request - это просто функция-утилита, которая вызывает наш API бекенда. Весь файл доступен в react-boilerplate. Мы сделаем небольшое изменение в нем после заполнения нашего файла sagas.js.

Нам также нужно создать еще одну функцию для наблюдения функции getFeeds:

Как мы видим здесь, функция getFeeds вызывается, когда мы вызываем действие, содержащее константу FETCH_FEEDS_REQUEST.

Теперь давайте скопируем файл request.js из react-boilerplate в наше приложение внутрь папки app/utils, а затем изменим функцию request:

Я только что добавил несколько значений по умолчанию, которые помогут нам сократить код позже, поскольку нам не нужно передавать метод и заголовки каждый раз. Теперь нам нужно создать другой файл util внутри папки app/utils. Мы назовем этот файл socketSagas.js. Он будет содержать четыре функции: connectToSocket, joinChannel, createSocketChannel и handleUpdatedData.

Функция connectToSocket будет отвечать за подключение к нашему бекенд API. Мы будем использовать npm пакет phoenix . Поэтому нам нужно будет установить его:

npm install phoenix --save

Это установит npm пакет phoenix и сохранит его в нашем файле package.json. Функция connectToSocket будет выглядеть примерно так:

Затем мы определяем нашу функцию joinChannel, которая будет отвечать за присоединение определенного канала от бекенда. Функция joinChannel будет иметь следующее содержимое:

Если присоединение будет успешным, мы будем логировать 'Joined successfully' только для тестирования. Если во время фазы соединения произошла ошибка, мы также будем регистрировать это только для целей отладки.

CreateSocketChannel будет отвечать за создание канала событий из данного сокета.

Эта функция также будет полезна, если мы хотим отказаться от подписки на конкретном канале.

HandleUpdatedData будет просто вызывать действие, переданное ему в качестве аргумента.

Теперь добавим остальные sags в наш файл app/container/FeedsPage/sagas.js. Здесь мы создадим еще две функции: connectWithFeedsSocketForNewFeeds и watchConnectWithFeedsSocketForNewFeeds.

Функция connectWithFeedsSocketForNewFeeds будет отвечать за подключение к бекенд сокету и проверку новых фидов. Если есть какие-либо новые фиды, он вызовет функцию createSocketChannel из файла utils/socketSagas.js, который создаст канал событий для данного сокета. Функция connectWithFeedsSocketForNewFeeds будет содержать следующее:

И watchConnectWithFeedsSocketForNewFeeds будет иметь следующее:

Теперь мы свяжем все с нашим файлом app/container/FeedsPage/index.js. Этот файл будет содержать все элементы пользовательского интерфейса. Начнем с вызова prop, который будет извлекать данные из бекенда нашего componentDidMount:

Это приведет к загрузке всех фидов. Теперь нам нужно снова вызвать функцию fetchFeedsRequest, когда prop hasNewFeeds истен (вы можете обратиться к начальному состоянию нашего редюсера для структуры нашего приложения):

После этого мы просто рендерим фиды в нашей функции render. Мы создадим функцию feedsNode со следующим содержимым:

И тогда мы можем вызвать этот метод в нашем методе render:

Если мы перейдем на http://localhost:3000/feeds, мы увидим, что на нашей консоли:

Присоединился успешно

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

Не стесняйтесь ссылаться на мой коммит, потому что в нем выполнено многое!

Создайте форму для добавления нового фида

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

Начнем с добавления констант в app/containers/AddFeedPage/constants.js:

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

Контейнер AddFeedPage будет использовать четыре действия: updateAttributes, saveFeedRequest, saveFeed и saveFeedError. Функция updateAttributes обновит атрибуты нашего нового фида. Это означает, что всякий раз, когда мы вводим что-то в поле ввода заголовка и описания фида, функция updateAttributes обновляет наше состояние Redux. Эти четыре действия будут выглядеть примерно так:

Затем добавим функции редюсера в файл app/container/AddFeedPage/reducer.js. InitialState будет выглядеть следующим образом:

И функция редюсера будет выглядеть примерно так:

Затем мы будем конфигурировать наш файл app/container/AddFeedPage/selectors.js. Он будет иметь четыре селектора: title, description, error и saving. Как следует из названия, эти селекторы извлекают эти состояния из состояния Redux и делают его доступным в нашем контейнере в качестве props.

Эти четыре функции будут выглядеть следующим образом:

Затем, давайте настроим наши sagas для контейнера AddFeedPage. Он будет иметь две функции: saveFeed и watchSaveFeed. Функция saveFeed будет отвечать за выполнение запроса POST на наш API, и она будет содержать следующее:

Функция watchSaveFeed будет похожа на наши предыдущие функции наблюдателей:

Затем нам просто нужно отобразить форму в нашем контейнере. Чтобы сохранить модульность, давайте создадим подкомпонент для формы. Создайте новый файл form.js внутри нашей папки app/container/AddFeedPage/sub-components (папка sub-components - это новая папка, которую вам нужно будет создать). Он будет содержать форму с одним полем ввода для заголовка фида и одним текстовым полем для описания фида. Метод render будет иметь следующее содержимое:

Мы создадим еще две функции: handleChange и handleSubmit. Функция handleChange отвечает за обновление нашего состояния Redux всякий раз, когда мы добавляем некоторый текст, а функция handleSubmit вызывает наш API для сохранения данных в нашем состоянии Redux.

Функция handleChange содержит следующее:

И функция handleSubmit будет содержать следующее:

Здесь мы сохраняем данные и очищаем значения формы.

Теперь, вернемся к нашему файлу app/container/AddFeedPage/index.js, мы просто рендерим только что созданную форму.

Теперь весь наш кодинг завершен. Не стесняйтесь проверять мой коммит, если у вас есть какие-либо сомнения.

Завершение

Мы завершили строительство нашего приложения. Теперь мы можем посетить http://localhost:3000/feeds/new и добавить новые фиды, которые будут отображаться в реальном времени по адресу http://localhost:3000/feeds. Нам не нужно обновлять страницу, чтобы увидеть новые фиды. Вы также можете попробовать это, открыв http://localhost:3000/feeds на двух вкладках бок о бок и протестировать это!

Заключение

Это будет просто пример приложения, чтобы показать реальные возможности объединения Phoenix с React. В большинстве случаев мы используем данные в реальном времени, и это может помочь вам понять, как это происходит. Надеюсь, вы нашли этот учебник полезным.

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.