【问题标题】:React useState hook not consistently updating反应 useState 钩子没有持续更新
【发布时间】:2020-07-17 01:47:26
【问题描述】:

我开始将 websockets 与 this example(伴随 repo here)一起集成到现有的 React/Django 应用程序中。在那个 repo 中,websocket 接口位于websockets.js,并在containers/Chat.js 中实现。

我可以让该代码按原样正常工作。

然后我开始重新编写我的实现以使用 Hooks,并碰了壁。数据正确流过socket,正确到达每个客户端的handler,handler内部可以读取到正确的状态。在该处理程序中,我正在调用我的 useState 函数来使用传入数据更新状态。

最初我遇到了addMessage() 中的单个useState 函数不一致触发的问题(10 次中有1 次?)。我将我的一个 useState 钩子分成两个(一个用于当前消息,一个用于所有消息)。现在在addMessage() 从服务器接收数据后,我的setAllMessages 钩子只会更新我在其中输入消息的客户端 - 不会更新其他客户端。所有客户端都可以正确接收/记录数据,只是不运行setAllMessages 函数。

如果我推送到函数外部的一个空数组,它会按预期工作。所以这似乎是函数更新周期的问题,但我一直无法追踪它。

这是我的 websocket.js 版本:

class WebSocketService {
  static instance = null;

  static getInstance() {
    if (!WebSocketService.instance) {
      WebSocketService.instance = new WebSocketService();
    }
    return WebSocketService.instance;
  }

constructor() {
    this.socketRef = null;
    this.callbacks = {};
  }

  disconnect() {
    this.socketRef.close();
  }

  connect(chatUrl) {
    const path = `${URLS.SOCKET.BASE}${URLS.SOCKET.TEST}`;
    this.socketRef = new WebSocket(path);

    this.socketRef.onopen = () => {
      console.log('WebSocket open');
    };

    this.socketRef.onmessage = e => {
      this.socketNewMessage(e.data);
    };

    this.socketRef.onerror = e => {
      console.log(e.message);
    };

    this.socketRef.onclose = () => {
      this.connect();
    };
  }

  socketNewMessage(data) {
    const parsedData = JSON.parse(data);
    const { command } = parsedData;

    if (Object.keys(this.callbacks).length === 0) {
      return;
    }

    Object.keys(SOCKET_COMMANDS).forEach(clientCommand => {
      if (command === SOCKET_COMMANDS[clientCommand]) {
        this.callbacks[command](parsedData.presentation);
      }
    });
  }

  backend_receive_data_then_post_new(message) {
    this.sendMessage({
      command_for_backend: 'backend_receive_data_then_post_new',
      message: message.content,
      from: message.from,
    });
  }

  sendMessage(data) {
    try {
      this.socketRef.send(JSON.stringify({ ...data }));
    } catch (err) {
      console.log(err.message);
    }
  }



 addCallbacks(allCallbacks) {
    Object.keys(SOCKET_COMMANDS).forEach(command => {
      this.callbacks[SOCKET_COMMANDS[command]] = allCallbacks;
    });
  }

  state() {
    return this.socketRef.readyState;
  }
}

const WebSocketInstance = WebSocketService.getInstance();

export default WebSocketInstance;

这是我的Chat.js版本

export function Chat() {
  const [allMessages, setAllMessages] = useState([]);
  const [currMessage, setCurrMessage] = useState('');

  function waitForSocketConnection(callback) {
    setTimeout(() => {
      if (WebSocketInstance.state() === 1) {
        callback();
      } else {
        waitForSocketConnection(callback);
      }
    }, 100);
  }

  waitForSocketConnection(() => {
    const allCallbacks = [addMessage];
    allCallbacks.forEach(callback => {
      WebSocketInstance.addCallbacks(callback);
    });
  });


/*
 * This is the problem area
 * `incoming` shows the correct data, and I have access to all state
 * But `setAllMessages` only updates on the client I type the message into
 */
  const addMessage = (incoming) => {
    setAllMessages([incoming]);
  };

  // update with value from input
  const messageChangeHandler = e => {
    setCurrMessage(e.target.value);
  };

  // Send data to socket interface, then to server
  const sendMessageHandler = e => {
    e.preventDefault();
    const messageObject = {
      from: 'user',
      content: currMessage,
    };
    setCurrMessage('');
    WebSocketInstance.backend_receive_data_then_post_new(messageObject);
  };

  return (
    <div>
      // rendering stuff here
    </div>
  );
}

【问题讨论】:

  • 您在哪里处理收到的来自其他客户端的消息?
  • @DrewReese 任何新消息都来自addMessage() - 我可以在每个客户端上正确记录incoming

标签: javascript django reactjs websocket react-hooks


【解决方案1】:

无需使用钩子将所有内容重写为功能组件。

您应该在功能上分解它 - 主要(父级、类/FC)用于初始化并向负责呈现列表和输入的 2 个功能性子级/组件提供 [数据和] 方法(作为道具)(新消息)。

如果您仍然需要它...useEffect 是一个关键...因为所有代码都运行在功能组件中的每个渲染上...包括函数定义、重新定义、新引用、回调数组中的重复等。

您可以尝试将所有曾经定义的函数移动到useEffect

useEffect(() => {

  const waitForSocketConnection = (callback) => {
    ...
  }

  const addMessage = (incoming) => {
    setAllMessages([incoming]);
  };

  waitForSocketConnection(() => {
    ...
  }

}, [] ); // <<< RUN ONCE

【讨论】:

  • 是的,将组件分解为一个类和纯组件绝对有效。退后一步并记住这一点很好,所以谢谢。不过,我仍然对在最后一步导致问题的原因感到好奇。
  • 每次渲染上的新 addMessage 函数定义 > 总是新的 fn ref,与传递给 WebSocketInstance.addCallbacks 不同?
猜你喜欢
  • 1970-01-01
  • 2021-04-17
  • 2020-09-27
  • 2020-04-19
  • 2019-09-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-11
相关资源
最近更新 更多