Итак, нам нужно, чтобы клиентское приложение реагировало на события, генерируемые на сервере. Обычно в подобных случаях речь идёт о приложениях реального времени. В таком сценарии сервер передаёт клиенту свежие данные по мере их появления. После того, как между клиентом и сервером будет установлено соединение, сервер, не полагаясь на запросы клиента, самостоятельно инициирует передачу данных.
Подобная модель подходит для множества проектов. Среди них — чаты, игры, торговые системы, и так далее. Часто предложенный в этом материале подход используют для того, чтобы повысить скорость отклика системы, при том, что сама по себе такая система может функционировать и без него. Мотивы разработчика в данном случае не важны. Полагаем, допустимо предположить, что вы это читаете, так как вам хочется узнать о том, как использовать сокеты при создании React-приложений и приблизить вашу разработку к приложениям реального времени.
Здесь мы покажем очень простой пример. А именно — создадим сервер на Node.js, который может принимать подключения от клиентов, написанных на React, и отправлять в сокет, с заданной периодичностью, сведения о текущем времени. Клиент, получая свежие данные, будет выводить их на странице приложения. И клиент и сервер используют библиотеку Socket.io.
Настройка рабочей среды
Предполагается, что у вас установлены базовые инструменты, такие, как Node.js и NPM. Кроме того, вам понадобится NPM-пакет
create-react-app, поэтому, если его у вас ещё нет, установите его глобально такой командой:
npm --global i create-react-app
Теперь можно создать React-приложение
socket-timer, с которым мы будем экспериментировать, выполнив такую команду:
create-react-app socket-timer
Теперь приготовьте ваш любимый текстовый редактор и найдите папку, в которой расположены файлы приложения
socket-timer. Для того, чтобы его запустить, достаточно выполнить, с помощью терминала, команду npm start.
В нашем примере серверный и клиентский код будут расположены в одной папке, но подобное не стоит делать в рабочих проектах.
Socket.io на сервере
Создадим сервер, поддерживающий вебсокеты. Для того, чтобы это сделать, перейдите в терминал, переключитесь на папку приложения и установите Socket.io:
npm i --save socket.io
Теперь создайте файл
server.js в корне папки. В этом файле, для начала, импортируйте библиотеку и создайте сокет:
const io = require('socket.io')();
Теперь можно использовать переменную
io для работы с сокетами. Вебсокеты — это долгоживущие двусторонние каналы связи между клиентом и сервером. На сервере надо принять запрос на соединение от клиента и поддерживать подключение. Используя это соединение, сервер сможет публиковать (генерировать) события, которые будет получать клиент.
Сделаем следующее:
io.on('connection', (client) => {
// тут можно генерировать события для клиента
});
Далее, нужно сообщить Socket.io о том, на каком порту требуется ожидать подключения клиента.
const port = 8000;
io.listen(port);
console.log('listening on port ', port);
На данном этапе можно перейти в терминал и запустить сервер, выполнив команду
node server. Если всё сделано правильно, вы увидите сообщение об его успешном запуске: listening on port 8000.
Сейчас сервер бездействует. Доступ к каналу связи с клиентом имеется, но канал пока простаивает. Канал связи двусторонний, поэтому сервер может не только передавать клиенту данные, но и реагировать на события, которые генерирует клиент. Этот механизм можно рассматривать как серверный обработчик событий, привязанный к конкретному событию конкретного клиента.
Для того, чтобы завершить работу над серверной частью приложения, надо запустить таймер. Необходимо, чтобы сервис запускал новый таймер для каждого подключившегося к нему клиента, при этом нужно, чтобы клиент мог передать серверу сведения о том, с каким интервалом он хочет получать данные. Это важный момент, демонстрирующий возможности двусторонней связи клиента и сервера.
Отредактируйте код в server.js следующим образом:
io.on('connection', (client) => {
client.on('subscribeToTimer', (interval) => {
console.log('client is subscribing to timer with interval ', interval);
});
});
Теперь у нас есть базовая конструкция для организации подключения клиента и для обработки события запуска таймера, приходящего с клиента. Сейчас можно запустить таймер и начать транслировать события, содержащие сведения о текущем времени, клиенту. Снова отредактируйте серверный код, приведя его к такому виду:
io.on('connection', (client) => {
client.on('subscribeToTimer', (interval) => {
console.log('client is subscribing to timer with interval ', interval);
setInterval(() => {
client.emit('timer', new Date());
}, interval);
});
});
Тут мы открываем сокет и начинаем ожидать подключения клиентов. Когда клиент подключается, мы оказываемся в замыкании, где можно обрабатывать события от конкретного клиента. В частности, речь идёт о событии
subscribeToTimer, которое было сгенерировано на клиенте. Сервер, при его получении, запускает таймер с заданным клиентом интервалом. При срабатывании таймера событие timer передаётся клиенту.
В данный момент код в файле server.js должен выглядеть так:
const io = require('socket.io')();
io.on('connection', (client) => {
client.on('subscribeToTimer', (interval) => {
console.log('client is subscribing to timer with interval ', interval);
setInterval(() => {
client.emit('timer', new Date());
}, interval);
});
});
const port = 8000;
io.listen(port);
console.log('listening on port ', port);
Серверная часть проекта готова. Прежде чем переходить к клиенту, проверим, запускается ли, после всех правок, код сервера, выполнив в терминале команду
node server. Если, пока вы редактировали server.js, сервер был запущен, перезапустите его для проверки работоспособности последних изменений.
Socket.io на клиенте
React-приложение мы уже запускали, выполнив в терминале команду
npm start. Если оно всё ещё запущено, открыто в браузере, значит вы сможете внести изменения в код и браузер тут же перезагрузит изменённое приложение.
Сначала надо написать клиентский код для работы с сокетами, который будет взаимодействовать с серверным сокетом и запускать таймер, генерируя событие subscribeToTimer и потребляя события timer, которые публикует сервер.
Для того, чтобы это сделать, создайте файл api.js в папке src. В этом файле мы создадим функцию, которую можно будет вызвать для отправки серверу события subscribeToTimer и передачи данных события timer, генерируемого сервером, коду, который занимается визуализацией.
Начнём с создания функции и её экспорта из модуля:
function subscribeToTimer(interval, cb) {
}
export { subscribeToTimer }
Тут мы используем функции в стиле Node.js, где тот, кто вызывает функцию, может передать интервал таймера в первом параметре, а функцию обратного вызова — во втором.
Теперь нужно установить клиентскую версию библиотеки Socket.io. Сделать это можно из терминала:
npm i --save socket.io-client
Далее — импортируем библиотеку. Тут мы можем использовать синтаксис модулей ES6, так как выполняемый клиентский код транспилируется с помощью Webpack и Babel. Создать сокет можно, вызвав главную экспортируемую функцию из модуля
socket.io-client и передав в неё данные о сервере:
import openSocket from 'socket.io-client';
const socket = openSocket('http://localhost:8000');
Итак, на сервере мы ждём подключения клиента и события
subscribeToTimer, после получения которого запустим таймер, и, при каждом его срабатывании, будем генерировать события timer, передаваемые клиенту.
Теперь осталось лишь подписаться на событие timer, приходящее с сервера, и сгенерировать событие subscribeToTimer. Каждый раз, получая событие timer с сервера, будем выполнять функцию обратного вызова с данными события. В результате полный код api.js будет выглядеть так:
import openSocket from 'socket.io-client';
const socket = openSocket('http://localhost:8000');
function subscribeToTimer(cb) {
socket.on('timer', timestamp => cb(null, timestamp));
socket.emit('subscribeToTimer', 1000);
}
export { subscribeToTimer };
Обратите внимание на то, что мы подписываемся на событие
timer сокета до того, как генерируем событие subscribeToTimer. Делается это на тот случай, если мы столкнёмся с состоянием гонок, когда сервер уже начнёт выдавать события timer, а клиент на них ещё не подписан, что приведёт к потере данных, передаваемых в событиях.
Использование данных, полученных с сервера, в компоненте React
Итак, файл
api.js готов, он экспортирует функцию, которую можно вызвать для подписки на события, генерируемые сервером. Теперь поговорим о том, как использовать эту функцию в компоненте React для вывода, в реальном времени, данных, полученных с сервера через сокет.
При создании React-приложения с помощью create-react-app был сгенерирован файл App.js (в папке src). В верхней части кода этого файла добавим импорт ранее созданного API:
import { subscribeToTimer } from './api';
Теперь можно добавить в тот же файл конструктор компонента, внутри которого вызвать функцию
subscribeToTimer из api.js. Каждый раз, получая событие с сервера, просто запишем значение timestamp в состояние компонента, используя данные, пришедшие с сервера.
constructor(props) {
super(props);
subscribeToTimer((err, timestamp) => this.setState({
timestamp
}));
}
Так как мы собираемся использовать значение
timestamp в состоянии компонента, имеет смысл установить для него значение по умолчанию. Для этого добавьте следующий фрагмент кода ниже конструктора:
state = {
timestamp: 'no timestamp yet'
};
На данном этапе можно отредактировать код функции
render таким образом, чтобы она выводила значение timestamp:
render() {
return (
This is the timer value: {this.state.timestamp}
); }
Теперь, если всё сделано правильно, на странице приложения каждую секунду будут выводиться текущие дата и время, полученные с сервера.
Итоги
Я часто использую описанный здесь шаблон взаимодействия серверного и клиентского кода. Вы, без особых сложностей, можете расширить его для применения в собственных сценариях. Хочется надеяться, что теперь все, кто хотел приблизить свои React-разработки к приложениям реального времени, смогут это сделать.
Уважаемые читатели! Планируете ли вы применить описанную здесь методику клиент-серверного взаимодействия в своих проектах?
Комментарии (0)