Websocket 事件在 React 应用程序中接收旧的 redux 状态
Posted
技术标签:
【中文标题】Websocket 事件在 React 应用程序中接收旧的 redux 状态【英文标题】:Websocket event receiving old redux state in React app 【发布时间】:2021-02-22 05:04:10 【问题描述】:我正在使用 Reactjs 和 Redux 构建一个聊天应用程序。我有 2 个组件,分别称为 ChatHeads 和 ChatBox,它们同时并排安装。
在 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);
步骤:
-
两个组件都呈现后,我在 ChatHeads 组件中选择一个用户。
使用 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'
-
现在,每当 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,我确实看到商店使用正确的数据进行了更新。但是,在事件中,我仍然得到旧数据......
【参考方案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 props和useRef 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 调用或用户操作而改变。以上是关于Websocket 事件在 React 应用程序中接收旧的 redux 状态的主要内容,如果未能解决你的问题,请参考以下文章
React-Native Websocket事件数据属性丢失
webSocket.send() 没有在 react js 中执行?