websocket React无效的钩子调用:如何避免一个嵌套钩子?

xuo3flqw  于 2023-10-20  发布在  React
关注(0)|答案(3)|浏览(185)

如何在useEffect内部建立连接,同时使用自定义钩子装饰该连接?自定义钩子不允许在useEffect内部运行,并且在渲染期间不允许ref.current。什么是用React友好的接口 Package 连接的正确方法?
我相信在React中建立WebSocket连接的正确方法是使用useEffect钩子,沿着

  1. const connection = useRef(null);
  2. useEffect(() => {
  3. const ws = new WebSocket(socketUrl);
  4. connection.current = ws;
  5. return () => {
  6. ws.close();
  7. connection.current = null;
  8. }
  9. }, [socketUrl]);

以类似的方式,我设想创建一个RTCDataConnection,它遵循与.send('...')opencloseerrormessage事件相同的连接接口。
我正在寻找一种方法来为这些连接提供一个类似React的接口。我在考虑像下面这样的自定义useConnection钩子,它接受一个JavaScript连接对象,并返回两个React状态变量lastMessagereadyState,以及一个方法sendMessage

  1. export function useConnection(connection) {
  2. const [lastMessage, setLastMessage] = useState(null);
  3. const [readyState, setReadyState] = useState(null);
  4. const onReadyStateChange = useCallback(
  5. (e) => {
  6. console.info(e);
  7. setReadyState(connection.readyState);
  8. },
  9. [setReadyState]
  10. );
  11. const onMessage = useCallback(
  12. ({ data }) => {
  13. const message = JSON.parse(data);
  14. setLastMessage(message);
  15. },
  16. [setLastMessage]
  17. );
  18. function sendMessage(message) {
  19. const msgJSON = JSON.stringify(message);
  20. connection.send(msgJSON);
  21. }
  22. useEffect(() => {
  23. connection.addEventListener('open', onReadyStateChange);
  24. connection.addEventListener('close', onReadyStateChange);
  25. connection.addEventListener('error', onReadyStateChange);
  26. connection.addEventListener('message', onMessage);
  27. return () => {
  28. connection.removeEventListener('open', onReadyStateChange);
  29. connection.removeEventListener('close', onReadyStateChange);
  30. connection.removeEventListener('error', onReadyStateChange);
  31. connection.removeEventListener('message', onMessage);
  32. };
  33. }, [connection]);
  34. return { lastMessage, readyState, sendMessage };
  35. }

但是,我不知道如何正确地将钩子应用于连接。

  • 通过the rules of hooks,我不能在useEffect内部调用useConnection。它会向我抛出“无效的hook call”
  • 我也不能在useEffect之外引用connection.current,因为这违反了the rules of useRef:渲染期间不允许访问.current属性。

所以我的问题是,我如何将连接和挂钩结合在一起?或者我应该使用完全不同的方法?

osh3o9ms

osh3o9ms1#

如果你想这样做,例如。叫钩

  1. const {lastMessage, readyState, sendMessage} = useConnection(something);

useEffect内部,因为它是唯一可以访问something的地方,所以这是不可能的。
钩子应该在函数组件的主体中被调用,而不是有条件的,也不是在一个循环中,你可以把它们看作是一个导入。

解决方案:

创建一个状态connection并将其值作为useConnection函数参数

  1. const [connection, setConnection] = useState(null);
  2. const { lastMessage, readyState, sendMessage } = useConnection(connection);

**注意:**您可能需要在钩子中做一些额外的工作来处理收到的null连接,因为这将在组件首次挂载时调用钩子时发生

现在,从useEffect开始,而不是调用钩子,你只需要用你想要调用你的自定义钩子的值(即something)来更新connection状态。
当状态被更新时,组件会重新呈现,如果钩子函数接收到一个新的值作为参数,它会被调用,因此当connection得到一个新的值时,它会返回一个新的{lastMessage, readyState, sendMessage}值,在这个新的呈现中,你可以访问它。
因此,如果您想在更新connection状态后直接在useEffect中使用它们,那么这是不可能,但是,您仍然可以创建另一个useEffect,每次更新connection时都会触发它,所以在组件重新-通过在第一个useEffect中更新connection来呈现,因为它是一个新的呈现,所以您可以访问第二个useEffect

  1. useEffect(() => {
  2. // continue your logic here
  3. if(connection){
  4. sendMessage("new msg")
  5. }
  6. },[connection?.id])

当执行connection.send(msgJSON)时,该函数应该与预期的connection对象一起工作,因为它是一个接收预期的connection的新示例。在这里,我添加了connection?.id作为依赖项,而不是connection,因为我们想避免包含对象,这样的键应该存在于connection对象中。

注意useEffect钩子在每次更新其依赖数组中的值时都会触发,而且在组件首次挂载时也会触发。

展开查看全部
whhtz7ly

whhtz7ly2#

您可能希望在组件的顶层调用钩子,然后在useEffect()中使用它返回的部分

  1. const {lastMessage, readyState, sendMessage} = useConnection(connection);
  2. useEffect(() => {
  3. sendMessage("Hi");
  4. }, [sendMessage]);

你的方法看起来太棒了!

bq8i3lrv

bq8i3lrv3#

最后,我重新设计了useConnection,使其采用工厂函数,而不是直接采用连接对象。这不仅绕过了useRef,还使调用代码更加清晰(并且与the React devs views更加一致)。下面是如何使用useConnection钩子:

  1. const { lastMessage, readyState, sendMessage } = useConnection(
  2. () => new WebSocket(socketUrl)
  3. );

这就是全部。(如果socketUrl是可变的,则可以将其 Package 在useMemo中。)useConnection现在看起来像这样:

  1. /**
  2. * Provides a React-friendly interface around a JS connection.
  3. * It exposes two state variables: lastMessage and readyState,
  4. * and one method sendMessage(json).
  5. * factory is a factory function that creates a new connection.
  6. */
  7. export function useConnection(factory) {
  8. const empty = () => {};
  9. let sendMessage = empty;
  10. const [lastMessage, setLastMessage] = useState('');
  11. const [readyState, setReadyState] = useState(null);
  12. // The two event handlers do not depend on the connection
  13. // and are therefore declared outside `useEffect`.
  14. const handleMessage = ({ data }) => {
  15. const message = JSON.parse(data);
  16. setLastMessage(message);
  17. };
  18. const handleReadyStateChange = (e) => {
  19. console.debug(e);
  20. setReadyState(e.target.readyState);
  21. };
  22. // The effect, setting up and tearing down the connection,
  23. // depends on the factory function.
  24. useEffect(() => {
  25. let conn = null;
  26. const connect = () => {
  27. console.log('✅ Connecting...');
  28. conn = factory();
  29. conn.addEventListener('open', handleReadyStateChange);
  30. conn.addEventListener('close', handleReadyStateChange);
  31. conn.addEventListener('error', handleReadyStateChange);
  32. conn.addEventListener('message', handleMessage);
  33. sendMessage = send;
  34. };
  35. const disconnect = () => {
  36. console.log('❌ Disconnecting...');
  37. conn.removeEventListener('open', handleReadyStateChange);
  38. conn.removeEventListener('close', handleReadyStateChange);
  39. conn.removeEventListener('error', handleReadyStateChange);
  40. conn.removeEventListener('message', handleMessage);
  41. // Avoid an error message in the console saying:
  42. // "WebSocket is closed before the connection is established."
  43. if (conn.readyState === 1) {
  44. conn.close();
  45. }
  46. sendMessage = empty;
  47. setReadyState(conn.readyState);
  48. conn = null;
  49. };
  50. // The send method does depend on the connection and so
  51. // is declared inside `useEffect`.
  52. const send = (message) => {
  53. if (conn) {
  54. const msgJSON = JSON.stringify(message);
  55. conn.send(msgJSON);
  56. }
  57. };
  58. connect();
  59. return disconnect;
  60. }, [factory]);
  61. return { lastMessage, readyState, sendMessage };
  62. }

由于公认的答案与原来的问题较为接近,我将保持原样。

展开查看全部

相关问题