英文:
Managing complex data structure with React State
问题
客户端向服务器发送消息
    const sendMessage = (e) => {
        e.preventDefault();
        const payload = {
            date: new Date().toISOString(),
            message: inputBuffer,
        }
        
        if (inputBuffer && inputBuffer.trim() !== '')
            socket.emit("sendMessage", payload);
        
        setInputBuffer("");
    }
服务器接收消息并添加套接字ID,然后将其发送给管理员
    io.on("connection", (socket) => {
        console.log("CONNECTION");
        socket.on("sendMessage", (args) => {
            args["socket_id"] = socket.id;
            adminNS.to("adminRoom").emit("clientMessage", args)
        })
    });
现在发送的对象如下所示
{
  "date": "2023-08-08T19:32:11.999Z",
  "message": "abc",
  "socket_id": "G6e2gUJ5ExZzTaIlAAEP"
}
客户端的管理员面板监听此消息
    useEffect(() => {
        socketRef.current = io("/admin", {
            autoConnect: false,
            auth: {
                token: token
            }
        });
        socketRef.current.connect();
        socketRef.current.on("clientMessage", args => {
            setChats(chats => {
                const client = args.socket_id;
                let currentObj = chats;
                if (!currentObj[client]) {
                    currentObj[client] = [];
                    currentObj[client].messages = [];
                }
                    let newMessages = [];
                    newMessages.messages = currentObj[client].messages;
                    newMessages.messages.push(args.message);
                    currentObj[client] = newMessages;
                    return [currentObj];
            });
        })
        return () => {
            socketRef.current.removeEventListener("clientMessage");
        }
    }, [])
我的setChats调用当前概述了我希望对象的外观
此setter在每次更新时将args.message附加到chats[args.socket_id]的每个消息数组中
[
G6e2gUJ5ExZzTaIlAAEP: {
  messages: [
               "User1Message here", "SecondMessage"
            ]
},
socket_id2: {
  message: [
               "User2Message here"
           ]
}
]
问题是(我认为),setChat回调函数不是“纯”的,因此无法正常工作。
一旦我实现了这个数据结构,我就可以通过键和它们关联的消息/数组进行迭代,最终目标是拥有许多具有不同客户端聊天的聊天窗口。
我的问题是,我如何形成这样一个“复杂”的对象结构?也许我过于复杂化了问题,应该有单独的状态变量,并通过组件的返回部分中的循环逐个关联它们,但是键-多值关系似乎是最直接的。
我已经询问过,redux被提到作为一个解决方案,或者是react query。但是这真的不能在原生中实现吗?我想知道我在这里漏掉了什么。
英文:
The client sends a message to the server
    const sendMessage = (e) => {
        e.preventDefault();
        const payload = {
            date: new Date().toISOString(),
            message: inputBuffer,
        }
        
        if (inputBuffer && inputBuffer.trim() !== '')
            socket.emit("sendMessage", payload);
        
        setInputBuffer("");
    }
The server receives the message and adds on the socket id and emits it to admins
    io.on("connection", (socket) => {
        console.log("CONNECTION");
        socket.on("sendMessage", (args) => {
            args["socket_id"] = socket.id;
            adminNS.to("adminRoom").emit("clientMessage", args)
        })
    });
The object being emitted now looks like
{
  date: '2023-08-08T19:32:11.999Z',
  message: 'abc',
  socket_id: 'G6e2gUJ5ExZzTaIlAAEP'
}
The Admin panel on the client listens for this
    useEffect(() => {
        socketRef.current = io("/admin", {
            autoConnect: false,
            auth: {
                token: token
            }
        });
        socketRef.current.connect();
        socketRef.current.on("clientMessage", args => {
            setChats(chats => {
                const client = args.socket_id;
                let currentObj = chats;
                if (!currentObj[client]) {
                    currentObj[client] = [];
                    currentObj[client].messages = [];
                }
                    let newMessages = [];
                    newMessages.messages = currentObj[client].messages;
                    newMessages.messages.push(args.message);
                    currentObj[client] = newMessages;
                    return [currentObj];
            });
        })
        return () => {
            socketRef.current.removeEventListener("clientMessage");
        }
    }, [])
My setChats call currently outlines how I want the object to look
This setter is appending args.message into each messages array at chats[args.socket_id] every update
[
G6e2gUJ5ExZzTaIlAAEP: {
  messages: [
               "User1Message here", "SecondMessage"
            ]
},
socket_id2: {
  message: [
               "User2Message here"
           ]
}
]
The problem is (I think), the setChat callback function is not "pure" and as such isn't working.
Once I have this achieved this data structure I can just iterate through the keys and their associated messages / array, and the end goal is to have many chat windows with the different client chats.
My question is, how can I form a "complex" object structure like this? Maybe I'm overcomplicating the matter and should have seperate state variables and associate them iteratively via a loop in the return portion of the component, but a key - toManyValues relationship seems like the most straight forward.
I have asked around and redux has been mentioned as a solution, or react query. But can this really not be achieved natively? I'd like to know what I'm missing here
答案1
得分: 2
你正在改变chats,请不要这样做 -
socketRef.current.on("clientMessage", args => {
    setChats(chats => {
        const client = args.socket_id;
        return {
            ...chats,
            [client]: {
                ...(chats?.[client] ?? {}),
                messages: [
                    ...(chats?.[client]?.messages ?? []),
                    args.message,
                ],
            },
        }
     })
})
有点让人头疼吧?这就是为什么存在像ImmutableJS和其他项目一样的解决方案。
另一个建议是将你的状态扁平化。useState是廉价的 -
type ClientId = number
type Message = string
type Client = {
    id: ClientId
    name: string
}
const [clients, setClients] = useState<Map<ClientId, Client>>(() => new Map())
const [messages, setMessages] = useState<Map<ClientId, Array<Message>>>(() => new Map())
socketRef.current.on("clientMessage", args => {
    setMessages(msgs => {
        const client = args.socket_id
        return new Map(msgs).set(
            client,
            (next.get(client) ?? [])
                .concat([args.message])
        )
    })
})
没有使用TypeScript的情况下 -
const [clients, setClients] = useState(() => new Map())
const [messages, setMessages] = useState(() => new Map())
socketRef.current.on("clientMessage", args => {
    setMessages(msgs => {
        const client = args.socket_id
        return new Map(msgs).set(
            client,
            (next.get(client) ?? [])
                .concat([args.message])
        )
    })
})
英文:
You are mutating chats, don't do that -
socketRef.current.on("clientMessage", args => {
    setChats(chats => {
        const client = args.socket_id;
        return {
            ...chats,
            [client]: {
                ...(chats?.[client] ?? {}),
                messages: [
                    ...(chats?.[client]?.messages ?? []),
                    args.message,
                ],
            },
        }
     })
})
A bit of a nightmare? That's why projects like ImmutableJS and others exist.
Another recommendation would be to flatten your state. useState is cheap -
type ClientId = number
type Message = string
type Client = {
    id: ClientId
    name: string
}
const [clients, setClients] = useState<Map<ClientId, Client>>(() => new Map())
const [messages, setMessages] = useState<Map<ClientId, Array<Message>>>(() => new Map())
socketRef.current.on("clientMessage", args => {
    setMessages(msgs => {
        const client = args.socket_id
        return new Map(msgs).set(
            client,
            (next.get(client) ?? [])
                .concat([args.message])
        )
    })
})
Without TypeScript -
const [clients, setClients] = useState(() => new Map())
const [messages, setMessages] = useState(() => new Map())
socketRef.current.on("clientMessage", args => {
    setMessages(msgs => {
        const client = args.socket_id
        return new Map(msgs).set(
            client,
            (next.get(client) ?? [])
                .concat([args.message])
        )
    })
})
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论