【发布时间】:2021-12-11 21:36:11
【问题描述】:
我有一个使用 Firebase v9 的简单聊天应用程序,这些组件从父级到子级按以下层次顺序排列:ChatSection、Chat、ChatLine、EditMessage。
我有一个名为useChatService 的自定义钩子在状态下保存messages 的列表,该钩子在ChatSection 中调用,该钩子返回messages,我将它们从ChatSection 传递给Chat,然后我循环遍历 messages 并为每条消息创建一个 ChatLine 组件。
我可以单击每条消息前面的Edit 按钮,它显示EditMessage 组件以便我可以编辑文本,然后当我按“Enter”时,函数updateMessage 被执行并更新消息在数据库中,但随后每个 ChatLine 都会再次重新渲染,随着列表变大,这是一个问题。
编辑 2:我已经完成了使用 Firebase v9 制作工作示例的代码,因此您可以在每次(添加、编辑或删除)消息之后可视化我正在谈论的重新呈现.我正在使用 ReactDevTools Profiler 来跟踪重新渲染。
- 这里是完整的更新代码:CodeSandbox
- 还部署在:Netlify
ChatSection.js:
import useChatService from "../hooks/useChatService";
import { useEffect } from "react";
import Chat from "./Chat";
import NoChat from "./NoChat";
import ChatInput from "./ChatInput";
const ChatSection = () => {
let unsubscribe;
const { getChatAndUnsub, messages } = useChatService();
useEffect(() => {
const getChat = async () => {
unsubscribe = await getChatAndUnsub();
};
getChat();
return () => {
unsubscribe?.();
};
}, []);
return (
<div>
{messages.length ? <Chat messages={messages} /> : <NoChat />}
<p>ADD A MESSAGE</p>
<ChatInput />
</div>
);
};
export default ChatSection;
Chat.js:
import { useState } from "react";
import ChatLine from "./ChatLine";
import useChatService from "../hooks/useChatService";
const Chat = ({ messages }) => {
const [editValue, setEditValue] = useState("");
const [editingId, setEditingId] = useState(null);
const { updateMessage, deleteMessage } = useChatService();
return (
<div>
<p>MESSAGES :</p>
{messages.map((line) => (
<ChatLine
key={line.id}
line={line}
editValue={line.id === editingId ? editValue : ""}
setEditValue={setEditValue}
editingId={line.id === editingId ? editingId : null}
setEditingId={setEditingId}
updateMessage={updateMessage}
deleteMessage={deleteMessage}
/>
))}
</div>
);
};
export default Chat;
ChatInput:
import { useState } from "react";
import useChatService from "../hooks/useChatService";
const ChatInput = () => {
const [inputValue, setInputValue] = useState("");
const { addMessage } = useChatService();
return (
<textarea
onKeyPress={(e) => {
if (e.key === "Enter") {
e.preventDefault();
addMessage(inputValue);
setInputValue("");
}
}}
placeholder="new message..."
onChange={(e) => {
setInputValue(e.target.value);
}}
value={inputValue}
autoFocus
/>
);
};
export default ChatInput;
ChatLine.js:
import EditMessage from "./EditMessage";
import { memo } from "react";
const ChatLine = ({
line,
editValue,
setEditValue,
editingId,
setEditingId,
updateMessage,
deleteMessage,
}) => {
return (
<div>
{editingId !== line.id ? (
<>
<span style={{ marginRight: "20px" }}>{line.id}: </span>
<span style={{ marginRight: "20px" }}>[{line.displayName}]</span>
<span style={{ marginRight: "20px" }}>{line.message}</span>
<button
onClick={() => {
setEditingId(line.id);
setEditValue(line.message);
}}
>
EDIT
</button>
<button
onClick={() => {
deleteMessage(line.id);
}}
>
DELETE
</button>
</>
) : (
<EditMessage
editValue={editValue}
setEditValue={setEditValue}
setEditingId={setEditingId}
editingId={editingId}
updateMessage={updateMessage}
/>
)}
</div>
);
};
export default memo(ChatLine);
EditMessage.js:
import { memo } from "react";
const EditMessage = ({
editValue,
setEditValue,
editingId,
setEditingId,
updateMessage,
}) => {
return (
<div>
<textarea
onKeyPress={(e) => {
if (e.key === "Enter") {
// prevent textarea default behaviour (line break on Enter)
e.preventDefault();
// updating message in DB
updateMessage(editValue, setEditValue, editingId, setEditingId);
}
}}
onChange={(e) => setEditValue(e.target.value)}
value={editValue}
autoFocus
/>
<button
onClick={() => {
setEditingId(null);
setEditValue(null);
}}
>
CANCEL
</button>
</div>
);
};
export default memo(EditMessage);
useChatService.js:
import { useCallback, useState } from "react";
import {
collection,
onSnapshot,
orderBy,
query,
serverTimestamp,
updateDoc,
doc,
addDoc,
deleteDoc,
} from "firebase/firestore";
import { db } from "../firebase/firebase-config";
const useChatService = () => {
const [messages, setMessages] = useState([]);
/**
* Get Messages
*
* @returns {Promise<Unsubscribe>}
*/
const getChatAndUnsub = async () => {
const q = query(collection(db, "messages"), orderBy("createdAt"));
const unsubscribe = onSnapshot(q, (snapshot) => {
const data = snapshot.docs.map((doc, index) => {
const entry = doc.data();
return {
id: doc.id,
message: entry.message,
createdAt: entry.createdAt,
updatedAt: entry.updatedAt,
uid: entry.uid,
displayName: entry.displayName,
photoURL: entry.photoURL,
};
});
setMessages(data);
});
return unsubscribe;
};
/**
* Memoized using useCallback
*/
const updateMessage = useCallback(
async (editValue, setEditValue, editingId, setEditingId) => {
const message = editValue;
const id = editingId;
// resetting state as soon as we press Enter
setEditValue("");
setEditingId(null);
try {
await updateDoc(doc(db, "messages", id), {
message,
updatedAt: serverTimestamp(),
});
} catch (err) {
console.log(err);
}
},
[]
);
const addMessage = async (inputValue) => {
if (!inputValue) {
return;
}
const message = inputValue;
const messageData = {
// hardcoded photoURL, uid, and displayName for demo purposes
photoURL:
"https://lh3.googleusercontent.com/a/AATXAJwNw_ECd4OhqV0bwAb7l4UqtPYeSrRMpVB7ayxY=s96-c",
uid: keyGen(),
message,
displayName: "John Doe",
createdAt: serverTimestamp(),
updatedAt: null,
};
try {
await addDoc(collection(db, "messages"), messageData);
} catch (e) {
console.log(e);
}
};
/**
* Memoized using useCallback
*/
const deleteMessage = useCallback(async (idToDelete) => {
if (!idToDelete) {
return;
}
try {
await deleteDoc(doc(db, "messages", idToDelete));
} catch (err) {
console.log(err);
}
}, []);
const keyGen = () => {
const s = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
return Array(20)
.join()
.split(",")
.map(function () {
return s.charAt(Math.floor(Math.random() * s.length));
})
.join("");
};
return {
messages,
getChatAndUnsub,
updateMessage,
addMessage,
deleteMessage,
};
};
export default useChatService;
当使用updateMessage 方法更新消息时,我只需要重新渲染受影响的ChatLine(添加和删除相同),而不是列表中的每个ChatLine,同时保持messages 状态通过从ChatSection 到Chat,我知道ChatSection 和Chat 应该重新渲染,但不是列表中的每个ChatLine。 (也记住了ChatLine)
编辑 1:我想问题出在 useChatService.js 中的 setMessages(data),但我认为 React 只会重新渲染编辑的行,因为我在循环通过 Chat 组件中的 messages 时已经提供了 key={line.id},但我不知道如何解决这个问题。
【问题讨论】:
标签: javascript reactjs react-hooks