Правильный способ использования React-хуков + WebSockets - PullRequest
2 голосов
/ 10 февраля 2020

Мне нужно подключиться к серверу WebSockets и регистрировать его сообщения. С компонентом класса React я поместил бы эту логику c в componentDidMount ловушку жизненного цикла и продолжил бы счастливо, но я не уверен, как правильно реализовать ее с помощью ловушек.

Вот моя первая попытка.

import React, {useEffect} from 'react';

export default function AppWs() {
  useEffect(() => {
    let ws = new WebSocket('wss://ws.kraken.com/');
    ws.onopen = () => console.log('ws opened');
    ws.onclose = () => console.log('ws closed');

    ws.onmessage = e => {
      const message = JSON.parse(e.data);
      console.log('e', message);
    };

    return () => {
      ws.close();
    }
  }, []);

  return (
    <div>hooks + ws</div>
  )
}

Я добавил логин подключений и журналов c к useEffect, предоставил пустой массив с зависимостями, и все работало отлично. Пока мне не нужно было добавить pause состояние, чтобы приостановить ведение журнала.

export default function AppWs() {
  const [isPaused, setPause] = useState(false);

  useEffect(() => {
    let ws = new WebSocket('wss://ws.kraken.com/');
    ws.onopen = () => console.log('ws opened');
    ws.onclose = () => console.log('ws closed');

    ws.onmessage = e => {
      if (isPaused) return;
      const message = JSON.parse(e.data);
      console.log('e', message);
    };

    return () => {
      ws.close();
    }
  }, []);

  return (
    <div>
      <button onClick={() => setPause(!isPaused)}>{isPaused ? 'Resume' : 'Pause'}</button>
    </div>
  )
}

ESLint начал кричать на меня, что я должен добавить isPaused состояние как зависимость к useEffect.
Ну, хорошо , готово.
Но я заметил повторное подключение к серверу WS после каждого нажатия кнопки. Это явно не то, что я хочу.

Моя следующая итерация состояла в том, чтобы использовать два useEffect s: один для соединения и один для обработки сообщений.

export default function AppWs() {
  const [isPaused, setPause] = useState(false);
  const [ws, setWs] = useState(null);

  useEffect(() => {
    const wsClient = new WebSocket('wss://ws.kraken.com/');
    wsClient.onopen = () => {
      console.log('ws opened');
      setWs(wsClient);
    };
    wsClient.onclose = () => console.log('ws closed');

    return () => {
      wsClient.close();
    }
  }, []);

  useEffect(() => {
    if (!ws) return;

    ws.onmessage = e => {
      if (isPaused) return;
      const message = JSON.parse(e.data);
      console.log('e', message);
    };
  }, [isPaused, ws]);

  return (
    <div>
      <button onClick={() => setPause(!isPaused)}>{isPaused ? 'Resume' : 'Pause'}</button>
    </div>
  )
}

Это работает, как и ожидалось, но У меня такое чувство, что я что-то упускаю и эту задачу можно решить проще, с одним useEffect. Пожалуйста, помогите изменить код, чтобы убедить меня, что я правильно использую React-хуки. Спасибо!

1 Ответ

6 голосов
/ 11 февраля 2020

Поскольку вы устанавливаете веб-сокет только один раз, я думаю, что лучше использовать вместо ссылки состояние ref:

export default function AppWs() {
    const [isPaused, setPause] = useState(false);
    const ws = useRef(null);

    useEffect(() => {
        ws.current = new WebSocket("wss://ws.kraken.com/");
        ws.current.onopen = () => console.log("ws opened");
        ws.current.onclose = () => console.log("ws closed");

        return () => {
            ws.current.close();
        };
    }, []);

    useEffect(() => {
        if (!ws.current) return;

        ws.current.onmessage = e => {
            if (isPaused) return;
            const message = JSON.parse(e.data);
            console.log("e", message);
        };
    }, [isPaused]);

    return (
        <div>
            <button onClick={() => setPause(!isPaused)}>
                {isPaused ? "Resume" : "Pause"}
            </button>
        </div>
    );
}

Порядок useEffect важен.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...