使用React State管理复杂的数据结构

huangapple go评论78阅读模式
英文:

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(&quot;clientMessage&quot;, args =&gt; {
    setChats(chats =&gt; {
        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&lt;Map&lt;ClientId, Client&gt;&gt;(() =&gt; new Map())

const [messages, setMessages] = useState&lt;Map&lt;ClientId, Array&lt;Message&gt;&gt;&gt;(() =&gt; new Map())
socketRef.current.on(&quot;clientMessage&quot;, args =&gt; {
    setMessages(msgs =&gt; {
        const client = args.socket_id
        return new Map(msgs).set(
            client,
            (next.get(client) ?? [])
                .concat([args.message])
        )
    })
})

Without TypeScript -

const [clients, setClients] = useState(() =&gt; new Map())

const [messages, setMessages] = useState(() =&gt; new Map())
socketRef.current.on(&quot;clientMessage&quot;, args =&gt; {
    setMessages(msgs =&gt; {
        const client = args.socket_id
        return new Map(msgs).set(
            client,
            (next.get(client) ?? [])
                .concat([args.message])
        )
    })
})

huangapple
  • 本文由 发表于 2023年8月9日 04:16:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/76862941.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定