【问题标题】:Websocket event receiving old redux state in React appWebsocket 事件在 React 应用程序中接收旧的 redux 状态
【发布时间】:2021-02-22 05:04:10
【问题描述】:

我正在使用 Reactjs 和 Redux 构建一个聊天应用程序。我有 2 个组件,分别称为 ChatHeadsChatBox,它们同时并排安装。

ChatHeads 组件中,可以选择 User(您要与之聊天的人),并且该选择以 chatInfo 的形式存储在 redux 存储中。

ChatHeads 组件:

function ChatHeads(props) {
  const {
    dispatch,
    userInfo,
    userId
  } = props;
  const [chatHeads, setChatHeads] = useState([]);

  const handleChatHeadSelect = (chatHead, newChat = false) => {
    dispatch(
      chatActions.selectChat({
        isNewChat: newChat,
        chatId: chatHead.chat._id,
        chatUser: chatHead.user
      })
    );
  };

  const loadChatHeads = async () => {
    const response = await services.getRecentChats(userId, userInfo);
    setChatHeads(response.chats);
  };

  useEffect(() => loadChatHeads(), [userInfo]);

  return (
    //   LOOPING THOUGH ChatHeads AND RENDERING EACH ITEM
    //   ON SELECT OF AN ITEM, handleChatHeadSelect WILL BE CALLED
  );
}

export default connect(
  (state) => {
    return {
      userInfo: state.userInfo,
      userId: (state.userInfo && state.userInfo.user && state.userInfo.user._id) || null,
      selectedChat: (state.chatInfo && state.chatInfo.chat && state.chatInfo.chat._id) || null
    };
  },
  null,
)(ChatHeads);

聊天操作和缩减程序:

const initialState = {
  isNewChat: false,
  chatId: '',
  chatUser: {},
};

const chatReducer = (state = initialState, action) => {
  let newState;
  switch (action.type) {
    case actions.CHAT_SELECT:
      newState = { ...action.payload };
      break;

    default:
      newState = state;
      break;
  }
  return newState;
};

export const selectChat = (payload) => ({
  type: actions.CHAT_SELECT,
  payload,
});

ChatBox 组件中,我正在建立与服务器的套接字连接,并基于来自全局存储和 ws 事件的chatInfo 对象,执行一些操作。

聊天框组件

let socket;

function ChatBox(props) {
  const { chatInfo } = props;

  const onWSMessageEvent = (event) => {
    console.log('onWSMessageEvent => chatInfo', chatInfo);
    // handling event
  };

  useEffect(() => {
    socket = services.establishSocketConnection(userId);

    socket.addEventListener('message', onWSMessageEvent);

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

  return (
    //   IF selectedChatId
    //     THEN RENDER CHAT
    //   ELSE
    //     BLANK SCREEN
  );
}

export default connect((state) => {
  return {
    chatInfo: state.chatInfo
  };
}, null)(ChatBox);

步骤

  1. 两个组件都呈现后,我在 ChatHeads 组件中选择一个用户。
  2. 使用 Redux DevTools,我能够观察到 chatInfo 对象已正确填充。
chatInfo: {
    isNewChat: false,
    chatId: '603326f141ee33ee7cac02f4',
    chatUser: {
        _id: '602a9e589abf272613f36925',
        email: 'user2@mail.com',
        firstName: 'user',
        lastName: '2',
        createdOn: '2021-02-15T16:16:24.100Z',
        updatedOn: '2021-02-15T16:16:24.100Z'
    }
}
  1. 现在,每当 ChatBox 组件中触发 'message' 事件时,我的期望是 chatInfo 属性应该具有最新值。但是,我总是得到 initialState 而不是更新的。
chatInfo: {
    isNewChat: false,
    chatId: '',
    chatUser: {}
}

我在这里缺少什么?请建议...

【问题讨论】:

  • 嘿兄弟..请查看ChatBox Component,您在onWSMessageEvent 中记录userInfo,这可能总是相同.. 我认为您应该从event 获取数据
  • 我将chatInfo 记录在onWSMessageEvent 函数中。每当有 ws 事件时,我都会尝试使用它(我从 Redux 商店收到)。这里的问题是我没有得到更新的数据。
  • 你有没有直接尝试return {... action.payload }而不是在case actions.CHAT_SELECT:中分配给newState??
  • 这没有帮助!正如我所提到的,使用 Redux DevTools,我确实看到商店使用正确的数据进行了更新。但是,在事件中,我仍然得到旧数据......

标签: javascript reactjs redux websocket react-redux


【解决方案1】:

这种行为的原因是当你声明你的回调时

const { chatInfo } = props;
const onWSMessageEvent = (event) => {
  console.log('onWSMessageEvent => chatInfo', chatInfo);
  // handling event
};

它记得chatInfo 在声明的这一刻是正确的(这是初始渲染)。值在 store 内和组件渲染范围内更新与回调无关,重要的是回调范围以及声明回调时chatInfo 所指的内容。

如果您想创建一个始终可以读取最新状态/道具的回调,您可以将chatInfo 保留在可变引用中。

const { chatInfo } = props;
// 1. create the ref, set the initial value
const chatInfoRef = useRef(chatInfo);
// 2. update the current ref value when your prop is updated
useEffect(() => chatInfoRef.current = chatInfo, [chatInfo]);
// 3. define your callback that can now access the current prop value
const onWSMessageEvent = (event) => {
  console.log('onWSMessageEvent => chatInfo', chatInfoRef.current);
};

你可以查看this codesandbox,看看使用ref和直接使用prop的区别。

您可以咨询docs about stale propsuseRef docs


从广义上讲,问题在于您正试图在一个范围更窄的组件内管理全局订阅(套接字连接)。

没有useRef 的另一种解决方案看起来像

useEffect(() => {
    socket = services.establishSocketConnection(userId);

    socket.addEventListener('message', (message) => handleMessage(message, chatInfo));

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

在这种情况下,消息事件处理程序通过参数传递必要的信息,并且每次我们得到一个新的chatInfo 时,useEffect 挂钩都会重新运行。

但是,这可能与您的目标不符,除非您想为每个聊天打开一个单独的套接字,并在每次切换到不同的聊天时关闭该套接字。

因此,“适当的”解决方案需要在您的项目中向上移动套接字交互。一个提示是您正在使用 userId 打开套接字,这意味着它应该在您知道您的 userId 后运行,而不是在用户选择聊天时运行。

要向上移动交互,您可以将传入的消息存储在 redux 存储中,并通过 props 将消息传递给 ChatBox 组件。或者您可以在ChatHeads 组件中创建连接到套接字并将消息传递给ChatBox。类似的东西

function ChatHeads(props) {
  const {
    dispatch,
    userInfo,
    userId
  } = props;
  const [chatHeads, setChatHeads] = useState([]);

  const loadChatHeads = async () => {
    const response = await services.getRecentChats(userId, userInfo);
    setChatHeads(response.chats);
  };

  useEffect(() => loadChatHeads(), [userInfo]);

  const [messages, setMessages] = useState([]);
  useEffect(() => {
    socket = services.establishSocketConnection(userId);
    socket.addEventListener('message', (msg) => setMessages(messages.concat(msg)));
  }, [userId]);
    return () => socket.close();
}

   return (
   // render your current chat and pass the messages as props
   )

或者您可以创建一个 reducer 并发送一个 chatActions.newMessage 事件,然后使用 redux 将消息发送到当前聊天。

主要的一点是,如果你需要chatInfo来打开socket,那么每次chatInfo改变时,你可能需要打开一个新的socket,所以将依赖添加到useEffect钩子是有意义的.如果它只依赖于userId,则将其移至您获得userId 的位置并连接到那里的套接字。

【讨论】:

  • 这解决了这个问题。但是,如果我有一些我想在回调中使用的变量,我是否应该以类似的方式为所有这些变量创建引用。有什么优雅的方法可以解决这个问题吗?
  • @SreekarMouli 我添加了一些我认为可以帮助您的提示。
  • 感谢您的解决方案。另一个快速的疑问。在父组件中,我使用useRef 创建了一个socket 变量。我将这个socket 传递给我想收听一些事件的子组件。但是,socket.current 为空。在socket.current 具有 ws 连接后,如何让子组件重新渲染。我不能在子组件的useEffect 中使用socket.current,因为可变对象不能用作依赖项,我猜?
  • 这不是一门精确的科学,很难说,但我会仔细检查为什么有必要让子组件处理套接字。我通常会尝试在一个地方进行套接字交互,然后在套接字和应用程序之间创建一些粘合剂。您的应用似乎是关于聊天的,因此组件可能应该传递聊天、消息、用户等。
  • 换句话说,当您的套接字收到消息时,使用它来更新您的应用程序状态。然后组件使用此状态来呈现 UI 并处理交互。他们不关心状态是否因套接字消息、setTimeout 触发器、API 调用或用户操作而改变。
猜你喜欢
  • 2018-02-16
  • 1970-01-01
  • 1970-01-01
  • 2022-06-14
  • 2020-12-26
  • 1970-01-01
  • 1970-01-01
  • 2021-04-03
  • 1970-01-01
相关资源
最近更新 更多