Next.js+Material UI响应式抽屉:页面切换Logo重绘问题求助
Next.js + Material UI 侧边栏Logo页面切换时短暂消失问题
问题描述
在Next.js项目中使用Material UI响应式抽屉,点击侧边栏链接切换页面时,顶部Logo会短暂消失后重新显示,但下方的kitten图片无此问题。相关实现代码如下:
import ListAltIcon from "@mui/icons-material/ListAlt"; import SupervisorAccountIcon from "@mui/icons-material/SupervisorAccount"; import Box from "@mui/material/Box"; import Divider from "@mui/material/Divider"; import Drawer from "@mui/material/Drawer"; import List from "@mui/material/List"; import ListItem from "@mui/material/ListItem"; import ListItemButton from "@mui/material/ListItemButton"; import ListItemIcon from "@mui/material/ListItemIcon"; import ListItemText from "@mui/material/ListItemText"; import Toolbar from "@mui/material/Toolbar"; import Image from "next/image"; import logo from "../../../assets/logo.svg"; import kitten from "../../../assets/300.jpeg"; import { pages } from "../../../constants"; import StyledLink from "./sidebar.styled"; interface Props { handleDrawerToggle?: () => void; mobileOpen: boolean; drawerWidth: number; } const SidebarInner = () => ( <div> <Toolbar> <Image src={logo} alt=" logo" width="120" height="64" className="logo-image" style={{ margin: "auto" }} priority={true} /> </Toolbar> <Divider /> <List sx={{ textDecoration: "none" }}> {Object.values(pages).map(({ name, url }, index) => ( <ListItem key={url} disablePadding> <StyledLink href={url}> <ListItemButton> <ListItemIcon> {index % 2 === 0 ? <ListAltIcon /> : <SupervisorAccountIcon />} </ListItemIcon> <ListItemText primary={name} /> </ListItemButton> </StyledLink> </ListItem> ))} </List> <Image src={kitten} alt="test image" style={{ margin: "auto" }} /> </div> ); export default function Sidebar(props: Props) { const { drawerWidth, handleDrawerToggle, mobileOpen } = props; return ( <Box sx={{ display: "flex" }}> <Box component="nav" sx={{ width: { sm: drawerWidth }, flexShrink: { sm: 0 } }} aria-label="mailbox folders" > <Drawer variant="temporary" open={mobileOpen} onClose={handleDrawerToggle} ModalProps={{ keepMounted: true, }} sx={{ display: { xs: "block", sm: "none" }, "& .MuiDrawer-paper": { boxSizing: "border-box", width: drawerWidth, }, }} > <SidebarInner /> </Drawer> <Drawer variant="permanent" sx={{ display: { xs: "none", sm: "block" }, "& .MuiDrawer-paper": { boxSizing: "border-box", width: drawerWidth, }, }} open > <SidebarInner /> </Drawer> </Box> </Box> ); }
问题分析
1. Logo与kitten图片表现差异的原因
- 宽高属性类型不匹配:Logo的
<Image>组件使用了字符串类型的width="120"和height="64",而Next.js的Image组件要求这两个属性为数字类型。类型不匹配会触发组件内部额外的布局计算,在页面切换时导致Logo重新渲染甚至重新加载;而kitten图片未指定宽高,采用了组件默认的自适应逻辑,没有触发额外的重绘逻辑。 - 图片格式处理差异:Logo是SVG矢量图,Next.js对SVG的处理逻辑与JPEG位图不同。SVG作为代码式资源,在组件重新渲染时可能被重新解析,而JPEG会被缓存为二进制资源,页面切换时直接读取缓存,不会出现消失现象。
- 重复渲染问题:
SidebarInner被两个Drawer组件(移动端临时抽屉、桌面端永久抽屉)同时引用。页面切换时,响应式布局的状态变化可能导致两个抽屉的显示状态切换,触发SidebarInner两次渲染,Logo因此被重复初始化,而kitten图片因缓存机制未受影响。
修复方案
方案1:修正Image组件宽高属性类型
将Logo的width和height改为数字类型,符合Next.js Image组件的要求,减少不必要的布局计算:
<Image src={logo} alt="logo" width={120} // 改为数字类型 height={64} // 改为数字类型 className="logo-image" style={{ margin: "auto" }} priority={true} />
方案2:合并Drawer渲染逻辑,避免重复渲染SidebarInner
将两个抽屉合并为一个,根据屏幕尺寸动态切换variant,确保SidebarInner只渲染一次:
import { useMediaQuery, Theme } from "@mui/material/styles"; export default function Sidebar(props: Props) { const { drawerWidth, handleDrawerToggle, mobileOpen } = props; const isMobile = useMediaQuery((theme: Theme) => theme.breakpoints.down("sm")); return ( <Box sx={{ display: "flex" }}> <Box component="nav" sx={{ width: { sm: drawerWidth }, flexShrink: { sm: 0 } }} aria-label="mailbox folders" > <Drawer variant={isMobile ? "temporary" : "permanent"} open={isMobile ? mobileOpen : true} onClose={handleDrawerToggle} ModalProps={{ keepMounted: true, }} sx={{ "& .MuiDrawer-paper": { boxSizing: "border-box", width: drawerWidth, }, }} > <SidebarInner /> </Drawer> </Box> </Box> ); }
方案3:用React.memo包裹SidebarInner,阻止不必要重渲染
通过React.memo缓存SidebarInner组件,仅当依赖变化时才重新渲染:
import React from "react"; const SidebarInner = React.memo(() => ( // 原SidebarInner内容保持不变 ));
内容的提问来源于stack exchange,提问作者Pikk
相关产品推荐
相关产品推荐

