如何避免React路由切换时SideBarLink组件重复渲染?
问题背景
在React项目中,SideBar组件通过循环SIDEBAR_LINKS数组渲染多个SideBarLink组件,首次加载时渲染次数与数组长度一致,但每次路由切换时,所有SideBarLink都会重复渲染。尝试使用React.memo优化,但未生效,需要在保留单独文件定义路由的前提下解决该问题。
原组件代码(参考)
SideBarLink组件
import React from "react"; import { Link, useLocation } from "react-router-dom"; import classNames from "classnames"; import { SideBarRouteLink } from "../../modules/Routes/SideBarRoutes"; const linkClass = "flex items-center gap-2 font-light px-3 py-2 hover:text-white hover:font-bold rounded-sm text-base"; interface SideBarLinkProps { link: SideBarRouteLink; } const SideBarLink: React.FC<SideBarLinkProps> = ({ link }) => { console.log("SideBarLink"); const { pathname } = useLocation(); return ( <Link to={link.path} className={classNames( "flex items-center relative", pathname === link.path ? "text-white font-bold" : "text-slate-100", linkClass )} > {pathname === link.path && ( <span className="absolute top-1/2 left-0 transform -translate-y-1/2 w-1 h-6 bg-white"></span> )} <div className="flex items-center gap-2"> <span className="text-xl"> <link.icon /> </span> <span>{link.label}</span> </div> </Link> ); }; export default SideBarLink;
SideBar组件
import React from "react"; import { SIDEBAR_LINKS } from "../../modules/Routes/SideBarRoutes"; import SideBarLink from "./SideBarLink"; const SideBarView: React.FC = () => { console.log("SideBar VIEEEW"); return ( <div className="bg-gradient-to-br from-blue-600 to-cyan-400 w-60 p-3 flex flex-col"> <div className="flex gap-2 justify-center px-1 py-8"> <span className="font-medium leading-10 tracking-widest text-white text-2xl"> KAROOOBAR </span> </div> <div className="border-t-2 border-b-2 border-gray-100 py-4 text-center"> <h1 className="text-base text-white">Sample Store</h1> </div> <div className="flex flex-col gap-0.5 py-8 "> {SIDEBAR_LINKS.map((link) => ( <SideBarLink key={link.key} link={link} /> ))} </div> <div className="flex flex-col gap-4 px-3"> <a href="mailto:karooobar@gmail.com" className="font-light text-white text-sm" > karooobar@gmail.com </a> <p className="font-light text-white text-sm">© 2023, Karooobar Inc.</p> </div> </div> ); }; export default SideBarView;
问题原因
- SideBarLink内部使用
useLocation钩子,路由切换时location对象更新,会触发所有依赖该钩子的组件重新渲染,React.memo默认仅对比props变化,无法拦截context/钩子依赖变化导致的重渲染。 - 自定义的memo比较函数仅对比
link对象引用,未考虑路由变化对组件渲染的实际影响——只有当当前路由与该link的path匹配关系改变时,组件才需要重新渲染,其他路由切换无需触发。
解决方案
步骤1:将路由状态提升到父组件
在SideBarView中获取当前pathname,作为props传递给每个SideBarLink,让SideBarLink不再直接依赖路由context。
修改后的SideBarView代码:
import React from "react"; import { useLocation } from "react-router-dom"; import { SIDEBAR_LINKS } from "../../modules/Routes/SideBarRoutes"; import SideBarLink from "./SideBarLink"; const SideBarView: React.FC = () => { console.log("SideBar VIEEEW"); const { pathname } = useLocation(); return ( <div className="bg-gradient-to-br from-blue-600 to-cyan-400 w-60 p-3 flex flex-col"> <div className="flex gap-2 justify-center px-1 py-8"> <span className="font-medium leading-10 tracking-widest text-white text-2xl"> KAROOOBAR </span> </div> <div className="border-t-2 border-b-2 border-gray-100 py-4 text-center"> <h1 className="text-base text-white">Sample Store</h1> </div> <div className="flex flex-col gap-0.5 py-8 "> {SIDEBAR_LINKS.map((link) => ( <SideBarLink key={link.key} link={link} currentPath={pathname} /> ))} </div> <div className="flex flex-col gap-4 px-3"> <a href="mailto:karooobar@gmail.com" className="font-light text-white text-sm" > karooobar@gmail.com </a> <p className="font-light text-white text-sm">© 2023, Karooobar Inc.</p> </div> </div> ); }; export default SideBarView;
步骤2:修改SideBarLink组件,优化React.memo逻辑
移除SideBarLink内部的useLocation,接收currentPath作为props,并优化memo比较函数——仅当组件的激活状态发生变化时才重新渲染。
修改后的SideBarLink代码:
import React from "react"; import { Link } from "react-router-dom"; import classNames from "classnames"; import { SideBarRouteLink } from "../../modules/Routes/SideBarRoutes"; const linkClass = "flex items-center gap-2 font-light px-3 py-2 hover:text-white hover:font-bold rounded-sm text-base"; interface SideBarLinkProps { link: SideBarRouteLink; currentPath: string; } const SideBarLink: React.FC<SideBarLinkProps> = React.memo( ({ link, currentPath }) => { console.log(`SideBarLink: ${link.label}`); return ( <Link to={link.path} className={classNames( "flex items-center relative", currentPath === link.path ? "text-white font-bold" : "text-slate-100", linkClass )} > {currentPath === link.path && ( <span className="absolute top-1/2 left-0 transform -translate-y-1/2 w-1 h-6 bg-white"></span> )} <div className="flex items-center gap-2"> <span className="text-xl"> <link.icon /> </span> <span>{link.label}</span> </div> </Link> ); }, (prevProps, nextProps) => { // 判断条件:link引用不变,且组件激活状态未改变 const wasActive = prevProps.currentPath === prevProps.link.path; const isActive = nextProps.currentPath === nextProps.link.path; return prevProps.link === nextProps.link && wasActive === isActive; } ); SideBarLink.displayName = "SideBarLink"; export default SideBarLink;
效果说明
修改后,路由切换时仅之前激活和当前激活的两个SideBarLink会重新渲染,其他未涉及状态变化的组件会被React.memo跳过,彻底避免不必要的重复渲染。
内容的提问来源于stack exchange,提问作者prajun7
相关产品推荐
相关产品推荐

