Rundown of React Hooks и их применение в симуляции чата в реальном времени
Хуки в React вызвали изменение в том, как разработчики React структурируют свои проекты; катализатор принятия функций вместо классов. В этой статье будут рассмотрены 2 крючка, useState
и useEffect
, для моделирования среды чата в реальном времени с использованием socket.io и Express с целью демонстрации того, как работают эти хуки.
Затем мы исследуем поведение наших хуков и продемонстрируем дополнительную демонстрацию, чтобы улучшить наше поведение при присоединении к комнате (спойлер: внизу статьи). Демонстрации можно легко воспроизвести, и после объяснения я перечислю команды для настройки проекта, которые могут использовать все желающие.
Не забегая вперед, давайте рассмотрим первую демонстрацию, где мы увидим, как useState
и useEffect
можно использовать с веб-сокетом для управления процессом монтирования, повторного рендеринга и размонтирования. Конечный результат следующий:
Вверху слева: Express Server. Внизу слева: консоль Javascript. Справа: приложение React
Обзор хуков
В приведенной выше демонстрации мы используем хуки следующим образом:
useState
useState
используется для установки переменных состояния наших компонентов. Мы определяем эти хуки, как только открываем объявление нашей функции. В этой демонстрации не используются классы, и нам не нужно использовать setState()
для обновления состояния:
function App() { const [messageCount, setMessageCount] = useState(0); const [theme, setTheme] = useState('dark'); const [inRoom, setInRoom] = useState(false); ...
Давайте посмотрим, как определяется useState
хук, а затем сравним его со стандартным синтаксисом React.
useState
использует синтаксис деструктурирующего присваивания (подробнее об этом здесь). Этот синтаксис позволяет нам распаковывать значения из массива в отдельные переменные. Для каждого описанного выше хука мы определяем 2 переменные в его массиве, которые затем можем использовать в нашей функцииApp
для ссылки и обновления значения состояния.- Первая переменная массива - это имя переменной, которая относится к нашему значению состояния.
- Вторая переменная массива относится к функции, вызываемой для обновления значения нашего состояния.
- Значения, переданные в
useState()
, являются значениями состояния по умолчанию. Состояние по умолчанию дляmessageCount
-0
, а значение по умолчанию дляtheme
-dark
.
Рассмотрим messageCount
. Вот как мы можем определить значение нашего состояния и функцию обновления в стандартном классе React:
class App extends React.component { state = { messageCount: 0 }; const messageCount = this.state.messageCount; setMessageCount = count => ( setState({messageCount: count}); ) ... }
По сути, это то же самое, что объявление хука useState
:
function App() { const [messageCount, setMessageCount] = useState(0); ... }
Используя useState
, мы можем увидеть, что наш синтаксис значительно сократился, при этом сохраняя удобочитаемость.
Это useState
. Теперь давайте посмотрим на второй крючок, useEffect
.
useEffect
Хук useEffect
служит совсем другой цели, чем useState
. Он имеет дело с побочными эффектами компонентов - например, материал, который обрабатывается в процессе монтажа и демонтажа компонента.
Другими словами, useEffect
- альтернатива использованию методов жизненного цикла класса React; в частности, три из них: componentDidMount
, componentDidUpdate
и componentWillUnmount
.
Как это выглядит с точки зрения синтаксиса?
useEffect
просто принимает функцию как единственный обязательный аргумент. Все в этом аргументе будет выполняться на фазахcomponentDidMount
иcomponentDidUpdate
.- Внутри этой функции мы можем вернуть другую функцию, которая действует как очистка компонента при размонтировании: например,
componentWillUnmount
фаза. - Наконец, после нашей функции
useEffect
также может принимать необязательный массив в качестве второго аргумента, содержащий значения состояния, которые должны измениться, чтобы произошла повторная визуализация.
Эти 3 характеристики useEffect
можно заключить в следующий шаблон:
function App() { useEffect(() => { //stuff that happens upon initial render ///and subsequent re-renders //e.g. make a fetch request, open a socket connection return () => { //stuff that happens when the component unmounts //e.g. close socket connection } }, [messageCount]); ... }
Как видите, useEffect
значительно упрощает чтение компонентов и управление ими: они, по сути, группируют связанный код жизненного цикла в одну функцию, хотя у нас все еще есть возможность определять несколько useEffects в одном компоненте. Определение множественных useEffects становится полезным при использовании второго аргумента массива useEffect
, что демонстрирует наша демонстрация.
К настоящему времени вы, возможно, уже догадались, как мы можем управлять подключением к веб-сокету, используя только хуки.
В любом случае, давайте посмотрим, как это делается в нашей следующей демонстрации.
Демо-версия App.js
Давайте посетим демонстрационное приложение React, чтобы укрепить наше понимание этих хуков. Демонстрация в основном состоит из стандартного create-react-app
стандартного кода по умолчанию, с некоторыми добавленными хуками и кнопками, а также с нашим socket.io
клиентом для обработки соединения через веб-сокет:
В довольно удобочитаемой форме компонент состоит из следующего:
Подключение через сокет, подключение к localhost: 3011. Номер вашего порта не имеет значения - просто убедитесь, что он соответствует вашему внутреннему интерфейсу socket.io. Мы обратимся к коду на стороне сервера позже в статье для полноты демонстрации.
useState
крючки. Как обсуждалось выше, наши 3 перехватчика состояния определяются сразу после открытия функции, безусловно.
Первая useEffect
ловушка
Наша первая useEffect
отвечает за присоединение и выход из комнаты. На этапе монтирования клиенту внутреннего веб-сокета отправляется событие join room
, предлагающее присоединиться к нашему test-room
. Здесь мы также обрабатываем входящие сообщения от test-room
, прослушивая receive message
события, которые транслируются с нашего сервера веб-сокетов. Такое обновление события увеличивает наше messageCount
значение состояния.
Блок возврата, вызываемый при отключении компонента, генерирует событие leave room
, чтобы покинуть test-room
.
Обратите внимание, что эти события происходят только тогда, когда значение нашего inRoom
состояния равно true
, что по умолчанию равно false
при первоначальном рендеринге. Далее по компоненту у нас есть кнопка, которая переключает это значение состояния. Это позволяет нам входить в комнату и выходить из нее, а не автоматически присоединяться, как только страница загружается.
useEffect
, как и все хуки, нужно определять безоговорочно, поэтому мы не можем сделать что-то вроде определения хука внутри if(inRoom)
, например. Вместо этого мы проверяем условия в хуке.
Вторая useEffect
ловушка
Эта useEffect
ловушка обновляет заголовок вкладки в браузере с нашим messageCount
значением состояния.
Обратите внимание, что ловушка обрабатывается только при повторном рендеринге, если messageCount
изменяется. Здесь мы использовали второй аргумент useEffect
, который принимает массив значений состояния, которые должны измениться, чтобы этот эффект обрабатывался при повторном рендеринге.
handleSetTheme, handleInRoom и handleNewMessage
Следующие 3 функции обрабатывают наши нажатия кнопок:
handleSetTheme
переключает нашу тему между светом и темнотой, что просто отражается как className при рендеринге.handleInRoom
переключает нашу переменную состоянияinRoom
между истиной и ложью.handleNewMessage
отправляет событиеnew message
клиенту веб-сокета нашего сервера, который, в свою очередь, передает событиеreceive message
всем клиентам в нашемtest-room
. Но разве мы еще не обработали событиеreceive message
в нашей ловушкеuseEffect
? Да, но события, отправленные отправителем, не отправляются обратно тому же отправителю. Поэтому вместо этого мы увеличиваем здесь нашу переменную состоянияmessageCount
.
возвращение
Наш блок возврата отображает домашнюю страницу приложения create-response-app по умолчанию. Были добавлены несколько заголовков и кнопок, чтобы отразить состояние нашей комнаты чата, а также класс темы, который переключает цвет фона страницы между светлым и темным.
В этом функциональном компоненте много чего происходит, но согласованность синтаксиса и макета делает его довольно читабельным. Бьюсь об заклад, тот, кто не пробовал работать с socket.io, быстро поймет, что здесь происходит, с первого взгляда.
Экспресс-сервер socket.io
Чтобы завершить это объяснение, давайте кратко посетим сервер Express, на котором размещена серверная логика socket.io. В app.js
наша конфигурация socket.io определяется под стандартным кодом Express:
Как видите, здесь мы определили наши события socket.io, соответствующие нашей конфигурации React.
Настройка проекта
Чтобы запустить проект самостоятельно, настройте приложение React и сервер Node с парой дополнительных установок. Давайте быстро пробежимся по ним.
Настройка React
Для стороны React сгенерируйте новое приложение с помощью create-response-app перед обновлением React до последней версии, чтобы обеспечить реализацию хуков.
Примечание. Если вы хотите использовать исключительно пряжу, сделайте это с помощью yarn add ‹package›.
create-react-app websockets cd websockets && yarn npm i react@latest npm i react-dom@latest npm i socket.io-client yarn start
У меня появляется привычка переводить новые проекты React в параллельный режим, как только они запускаются. Для этого откройте index.js
и измените свой ReactDOM.render()
на:
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
Наконец, скопируйте код App.js
из приведенного выше Gist. Не забудьте указать цвета своей темы в App.css:
.Theme-light { background-color: #aaa; } .Theme-dark { background-color: #282c34; }
Экспресс установка
На стороне Node JS запустите новый сервер Express с установкой socket.io:
express backend_websockets cd backend_websockets && yarn npm i socket.io node bin/www
Наконец, скопируйте код app.js
сверху. (строчные буквы app.js для стороны узла! App.js в верхнем регистре для стороны React.)
Теперь, когда оба приложения работают на localhost, вы можете приступить к тестированию.
Изучение console.log
Посмотрите, что происходит, когда мы переключаем кнопки.
- Канал на localhost: 3011 сохраняется при начальной загрузке страницы.
- На данном этапе мы не находимся в
test-room
. Следовательно, если мы продолжим и нажмем «Переключить тему», ни одно из наших присоединений к комнате или выход из нее не произойдет при повторном рендеринге компонента. - Теперь нажмите "Войти в комнату". Наше состояние
inRoom
переключается, после чего обрабатывается первая ловушкаuseEffect()
, и мы присоединяемся к нашемуtest-room
, генерируя событиеjoin room
. - Теперь, когда мы переключаем значение состояния приложения
theme
, повторно запускается повторная визуализация компонента, но теперь мы покидаем и снова присоединяемся кtest-room
. Обратите внимание, как наш второйuseEffect()
игнорирует этот повторный рендеринг, поскольку состояниеmessageCount
не изменилось. - Нажатие на «Создать новое сообщение» также имеет тот же эффект, вызывая повторный рендеринг, а также выход из комнаты и присоединение к ней снова.
Дополнительная проблема и решение
В качестве дополнительной задачи рассмотрите возможность сделать следующее для улучшения демонстрации:
Изолируйте переменные состояния theme
и newMessage
в отдельный компонент, который НЕ заставит нас уйти и повторно ввести test-room
после обновления этих переменных состояния.
Решение:
Решение здесь состоит в том, чтобы разделить состояние <Room />
и состояние <Message />
на отдельные компоненты, чтобы присоединение и вход в нашу комнату происходило только тогда, когда мы переключаем переменную состояния inRoom
(и если мы полностью размонтируем приложение).
Однако за это приходится платить, поскольку элемент управления темой теперь применяется только к нашему пользовательскому интерфейсу сообщений, а не ко всему приложению (хотя вы могли бы обойтись без этого с помощью некоторого умного CSS). Обновленное полное решение под названием App2.js выглядит следующим образом:
Я также изменил следующий CSS для улучшения представления в App.css:
body { background-color: #282c34; color: #fff; text-align: center; padding: 20px; } .App-header { min-height: 50vh; }
Куда пойти отсюда
Спасибо, что следили за этой демонстрацией двух наиболее обсуждаемых хуков React. Теперь вы должны понимать, как применять их к вашим собственным проектам и в контексте управления подключениями к веб-сокетам.
Чтобы узнать больше о встраивании Websocket в проекты React, я опубликовал другую статью, посвященную интеграции событий Websocket с React Context и Redux: