英文:
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])
)
})
})
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论