我正在使用Reactjs和Redux构建一个聊天应用程序。我有两个组件叫做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组件,我建立一个套接字连接到服务器,并基于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);
<<p>步骤/strong>:- 在两个组件都被渲染后,我在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'
}
}
- 现在,每当'message'事件在ChatBox中被触发时组件,我的期望是
chatInfo
属性应该具有最新的值。但是,我总是得到initialState
chatInfo: {
isNewChat: false,
chatId: '',
chatUser: {}
}
我在这里错过了什么?请建议…
这种行为的原因是当你声明你的回调
const { chatInfo } = props;
const onWSMessageEvent = (event) => {
console.log('onWSMessageEvent => chatInfo', chatInfo);
// handling event
};
它记得在声明的这一刻(这是初始渲染)chatInfo
是什么。对于回调来说,值在存储和组件渲染范围内更新并不重要,重要的是回调范围和当你声明回调时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);
};
你可以检查这个codesandbox,看看使用ref和直接使用prop的区别。
你可以查阅旧的道具和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
组件中创建连接到套接字,并将消息传递到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
的位置并连接到那里的套接字。