WebSocket事件在React应用程序中接收旧的redux状态

tct7dpnv  于 6个月前  发布在  React
关注(0)|答案(2)|浏览(79)

我正在使用Reactjs和Redux构建一个聊天应用程序。我有两个名为ChatHeadsChatBox的组件,它们同时并排安装。
ChatHeads组件中,可以选择User(您想与之聊天的对象),此选择存储在Redux存储中,作为chatInfo

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组件中,我正在建立一个到服务器的套接字连接,并基于全局存储中的chatInfo对象& ws事件,执行一些操作。

聊天框组件

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组件中选择一个用户。
1.使用Redux DevTools,我能够观察到chatInfo对象已经正确填充。

chatInfo: {
    isNewChat: false,
    chatId: '603326f141ee33ee7cac02f4',
    chatUser: {
        _id: '602a9e589abf272613f36925',
        email: '[email protected]',
        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: {}
}


我错过了什么?请建议...

tvokkenx

tvokkenx1#

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

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之间的区别。
你可以参考文档中关于过时的props和useRef文档
一般来说,问题在于您试图在一个范围更窄的组件中管理全局订阅(套接字连接)。
另一个没有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组件中创建connect to socket,并将消息向下传递到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来打开套接字,那么每次chatInfo改变时,你可能都要打开一个新的套接字,所以把依赖项添加到useEffect钩子上是有意义的。如果它只依赖于userId,那么把它移到你得到userId的地方,并连接到那里的套接字。

w3nuxt5m

w3nuxt5m2#

使用store.getState()获取最新状态
我有同样的问题,我在redux存储中有最新的值,但在socket中获得以前的/陈旧的值
以上有参考的回答对我不起作用
所以我使用store.getState()来获取当前/最新的状态;

socket.on("message", async (data: any) => {
         const state = store.getState();   // using this
       console.log(state.chatInfo)   // I get latest value here
}

字符串

相关问题