#reactjs #react-hooks #use-effect #use-context
Вопрос:
у меня тут интересная проблема. Я создаю приложение react, используя связь веб — сокета с сервером. Я создаю этот веб-сайт в крючке useEffect, который, следовательно, не может выполняться несколько раз, иначе у меня было бы несколько подключений. Однако в этом эффекте использования я намерен использовать некоторые переменные, которые на самом деле находятся в контексте (useContext). И когда значения контекста меняются, значения в useEffect , по понятным причинам, не обновляются. Я пробовал использовать useRef, но это не сработало. У тебя есть какие-нибудь идеи?
const ws = useReflt;WebSocketgt;(); useEffect(() =gt; { ws.current = new WebSocket("ws://localhost:5000"); ws.current.addEventListener("open", () =gt; { console.log("opened connection"); }); ws.current.addEventListener("message", (message) =gt; { const messageData: ResponseData = JSON.parse(message.data); const { response, reload } = messageData; if (typeof response === "string") { const event = new CustomEventlt;ResponseDatagt;(response, { detail: messageData, }); ws.current?.dispatchEvent(event); } else { if (reload !== undefined) { console.log("general info should reload now"); GeneralInfoContext.reload(reload); } console.log(messageData); } }); });
Веб — сокет хранится как ссылка для лучшего использования в различных функциях за пределами этого блока useEffect
Примечание: используемое значение контекста на самом деле является функцией GeneralInfoContext.reload()
Ответ №1:
Решение с эффектом раздельного использования
Вы можете разделить логику, которая открывает подключение websocket, на две части тот, который добавляет обработчик сообщений в отдельный useEffect
s — первый может выполняться один раз, в то время как второй может повторно присоединять событие каждый раз, когда меняется зависимость:
useEffect(() =gt; { ws.current = new WebSocket("ws://localhost:5000"); ws.current.addEventListener("open", () =gt; { console.log("opened connection"); }); }, []); useEffect(() =gt; { const socket = ws.current; if(!socket) throw new Error("Expected to have a websocket instance"); const handler = (message) =gt; { /*...*/ } socket.addEventListener("message", handler); // cleanup return () =gt; socket.removeEventListener("message", handler); }, [/* deps here*/])
Эффекты будут выполняться по порядку, поэтому второй эффект будет выполняться после того, как первый эффект уже установлен ws.current
.
Решение с обратным вызовом ref
В качестве альтернативы вы можете поместить обработчик в ссылку и обновить его по мере необходимости, а также ссылаться на ссылку при вызове события:
const handlerRef = useRef(() =gt; {}) useEffect(() =gt; { handlerRef.current = (message) =gt; { /*...*/ } // No deps here, can update the function on every render }); useEffect(() =gt; { ws.current = new WebSocket("ws://localhost:5000"); ws.current.addEventListener("open", () =gt; { console.log("opened connection"); }); const handlerFunc = (message) =gt; handlerRef.current(message); ws.current.addEventListener("message", handlerFunc); return () =gt; ws.current.removeEventListener("message", handlerFunc); }, []);
Важно, чтобы вы этого не делали addEventListener("message", handlerRef.current)
, так как это приведет только к прикреплению исходной версии функции — дополнительная (message) =gt; handlerRef.current(message)
оболочка необходима, чтобы каждое сообщение передавалось в последнюю версию функции обработчика.
Этот подход по-прежнему требует двух useEffect
, так как лучше не вводить handlerRef.current = /* func */
их непосредственно в логику рендеринга, так как рендеринг не должен иметь побочных эффектов.
Что использовать?
Лично мне нравится первый вариант, отсоединение и повторное подключение обработчиков событий должно быть безвредным (и в основном «бесплатным») и кажется менее сложным, чем добавление дополнительной ссылки.
Но второй вариант позволяет избежать необходимости в явном списке зависимостей, что тоже неплохо, особенно если вы не используете правило eslint для обеспечения исчерпывающих deps. (Хотя вы определенно должны быть)
Ответ №2:
Вы можете предоставить useEffect
список переменных и useEffect
запустить его повторно, когда эти переменные изменятся.
Это небольшой пример:
const [exampleState, setExampleState] = useStatelt;booleangt;(false); useEffect(() =gt; { console.log("exampleState was updated."); }, [exampleState]);
Пример с сайта reactjs:
useEffect(() =gt; { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () =gt; { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }, [props.friend.id]); // Only re-subscribe if props.friend.id changes
Комментарии:
1. Ну, я попытался добавить этот массив, попытался включить как GeneralInfoContext, так и .перезагрузка, но этот эффект все еще не срабатывает при каждом изменении, и я продолжаю получать значение функции по умолчанию (я знаю это, потому что я ввел в него журнал консоли, чтобы я мог отслеживать)
Ответ №3:
Вы должны передать пустой массив в качестве второго параметра в useEffect, так что в этом случае он становится похожим на логику componentDidMount() react
useEffect(() =gt; { ...your websocket code here }, [])