如何避免Next.js 14中Chat组件展开/收起时卸载重挂载
问题:Next.js 14中避免Chat组件展开/关闭时被卸载重挂载
在Next.js 14应用中存在一个Chat组件,要求不能被卸载或重挂载。当前移动端设置了展开/关闭按钮,但每次操作都会导致组件销毁并重新渲染,对话记录丢失,求解决方法。
现有代码
// 断点样式 const FirstRoot = styled(Paper)(({ theme }) => ({ marginInline: theme.spacing(0.5), overflowY: "auto", [theme.breakpoints.down("md")]: { height: "100%", width: "100%", }, [theme.breakpoints.between("md", "lg")]: { height: "100%", width: "70%", }, [theme.breakpoints.up("lg")]: { height: "100%", width: "70%", }, })); // Chat容器样式 interface ChatContainerProps { isChatOpen: boolean; } const ChatContainer = styled(Box)<ChatContainerProps>(({ isChatOpen }) => ({ position: "fixed", top: 0, left: 0, width: "100%", height: isChatOpen ? "100vh" : 0, display: isChatOpen ? "block" : "none", backgroundColor: "white", zIndex: 1201, })); // 展开/关闭状态 const [isChatOpen, setIsChatOpen] = useState(false); const toggleChat = (): void => { setIsChatOpen((prevState) => !prevState); }; // 渲染部分 <FirstRoot> {children} {!isChatOpen && ( <Box sx={{ display: { xs: "block", sm: "block", md: "none" }, // 仅移动端显示按钮 zIndex: 1201, position: "fixed", bottom: 25, right: 16, borderRadius: "50%", overflow: "hidden", }} > {/* 打开Chat的按钮 */} <Image alt="Open Chat" height={80} onClick={toggleChat} src="/kerry_avatar.png" style={{ cursor: "pointer" }} title="Open Chat" width={80} /> </Box> )} {/* isChatOpen为true时渲染Chat组件 */} {isChatOpen ? ( <Box sx={{ display: { xs: "block", sm: "block", md: "none" }, }} > <ChatContainer isChatOpen={isChatOpen}> <Chat onClose={toggleChat} /> {/* onClose关闭Chat */} </ChatContainer> </Box> ) : null} </FirstRoot>
解决方案
核心问题是条件渲染导致Chat组件被卸载,只要让组件始终存在,用CSS控制显示/隐藏即可保留组件状态。
1. 修改渲染逻辑,移除Chat组件的条件渲染
将原来仅在isChatOpen为true时渲染Chat的逻辑,改为始终渲染Chat组件,依赖ChatContainer的样式控制显示与隐藏。
修改后的渲染代码:
<FirstRoot> {children} {!isChatOpen && ( <Box sx={{ display: { xs: "block", sm: "block", md: "none" }, zIndex: 1201, position: "fixed", bottom: 25, right: 16, borderRadius: "50%", overflow: "hidden", }} > <Image alt="Open Chat" height={80} onClick={toggleChat} src="/kerry_avatar.png" style={{ cursor: "pointer" }} title="Open Chat" width={80} /> </Box> )} {/* 始终渲染Chat组件,通过容器样式控制显示隐藏 */} <Box sx={{ display: { xs: "block", sm: "block", md: "none" }, }} > <ChatContainer isChatOpen={isChatOpen}> <Chat onClose={toggleChat} /> </ChatContainer> </Box> </FirstRoot>
2. 优化容器样式(可选)
如果希望隐藏时完全不占用空间且不可交互,可以保留现有ChatContainer的样式逻辑:
isChatOpen为true时:height: 100vh、display: blockisChatOpen为false时:height: 0、display: none
这样组件在DOM中始终存在,但隐藏时不会被用户感知,同时保留内部状态(对话记录)。
其他可选方案
- 状态提升:将Chat组件的对话记录状态提升到父组件,即使Chat被卸载,重新渲染时也能从父组件获取状态恢复。但这种方法不如CSS控制直接,且会增加父组件的状态复杂度。
- 使用React Context:将对话记录存入Context,Chat组件从Context读取/更新状态,即使组件卸载重挂载也能恢复数据。但同样不如保持组件挂载的方案高效。
内容的提问来源于stack exchange,提问作者Ignacio García
相关产品推荐
相关产品推荐

