gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-03 08:27:08 26 4
gpt4 key购买 nike

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

ChatHeads 组件中,可以选择用户(您想要与之聊天的用户),并且此选择作为 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: '<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="8df8fee8ffbfcde0ece4e1a3eee2e0" rel="noreferrer noopener nofollow">[email protected]</a>',
firstName: 'user',
lastName: '2',
createdOn: '2021-02-15T16:16:24.100Z',
updatedOn: '2021-02-15T16:16:24.100Z'
}
}
  • 现在,每当 ChatBox 组件中触发“message”事件时,我的期望是 chatInfo 属性应该具有最新值。但是,我总是得到初始状态而不是更新后的状态。
  • 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);
    };

    您可以查看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]);

    在这种情况下,消息事件处理程序通过参数传递必要的信息,并且 useEffect每次我们获得新的 chatInfo 时,钩子(Hook)都会重新运行.

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

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

    要向上移动交互,您可以将传入消息存储在 redux 存储中并将消息传递到 ChatBox通过 props 组件。或者您可以在 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 的位置并连接到那里的套接字。

    关于javascript - Websocket 事件在 React 应用程序中接收旧的 redux 状态,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66310269/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com