You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React+Framer Motion折叠侧边栏中如何阻止静态图标组件重复渲染?

React+Framer Motion折叠侧边栏中如何阻止静态图标组件重复渲染?

刚接触React遇到这种“明明没改props却还是重渲染”的问题真的很闹心,我来帮你拆解下原因和可行的解决办法~

先搞清楚为什么会出现这种情况

你已经用了React.memo和useMemo来缓存图标,但还是出现重渲染,大概率是这几个细节没注意到:

  1. 传递JSX元素而非组件引用:你之前直接把<CalendarPlus {...props} />作为button_icon传递,哪怕用了useMemo缓存,一旦父组件(SidebarDiv)因为is_expanded切换而重渲染,还是有可能因为隐式依赖导致JSX元素的引用变化,React.memo的浅比较会误以为props变了。
  2. SidebarItem内部的隐式动态依赖:你代码里的motion.button用了is_page来控制颜色,但这个is_page是组件内部隐式获取的(比如用usePathname判断),如果这个值在切换侧边栏时意外变化,会触发motion.button的动画更新,连带里面的图标一起“被重渲染”。
  3. 父组件的motion props不稳定:包裹SidebarItem的motion.div的item_icon_motion_props如果是每次渲染都重新创建的对象,会导致父组件重渲染,进而触发子组件SidebarItem的重渲染。

具体解决步骤

1. 把图标从JSX元素改成组件类型传递

不要直接传渲染好的<CalendarPlus />,而是传组件本身,这样引用绝对稳定,React.memo能准确识别:

// SidebarItem.tsx
import { memo } from 'react';
import { useRouter } from 'next/router';
import { motion } from 'framer-motion';

// 重新定义props,让button_icon接收组件类型而非JSX元素
type ButtonProps = {
  buttonIcon: React.ComponentType<{ size?: number; className?: string; color?: string }>;
  buttonRoute: string;
  buttonText: string;
  isExpanded: boolean;
  isCurrentPage: boolean; // 把原来的is_page改成显式props传递
};

const SidebarItem = memo(function SidebarItem({
  buttonIcon: ButtonIcon, // 重命名为大写,符合组件命名规范
  buttonRoute,
  buttonText,
  isExpanded,
  isCurrentPage,
}: ButtonProps) {
  const router = useRouter();

  return (
    <motion.button
      animate={{ color: isCurrentPage ? '#698f3f' : '#384f1f' }}
      transition={{ duration: 0 }}
      onClick={() => router.push(`./${buttonRoute}`)}
    >
      {/* 在这里渲染图标组件,传递静态props */}
      <ButtonIcon size={30} className="p-0.5 ml-0.5 my-1" color="currentColor" />
      
      {isExpanded ? (
        <motion.span
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          transition={{ duration: 0.4 }}
        >
          {buttonText}
        </motion.span>
      ) : null}
    </motion.button>
  );
});

export default SidebarItem;

2. 父组件传递图标组件并缓存动态props

在SidebarDiv里,直接传CalendarPlus组件,同时用useMemo缓存item_icon_motion_props,避免父组件不必要的重渲染:

// SidebarDiv.tsx
import { useMemo } from 'react';
import { usePathname } from 'next/navigation';
import { motion } from 'framer-motion';
import SidebarItem from './SidebarItem';
import { CalendarPlus } from 'your-icon-library';

export default function SidebarDiv({ isExpanded }) {
  const pathname = usePathname();
  // 缓存motion组件的props,只有依赖变化时才更新
  const item_icon_motion_props = useMemo(() => ({
    // 这里写你的motion配置,比如 animate: { x: isExpanded ? 0 : -20 } 之类的
  }), [isExpanded]);

  // 显式计算当前页面状态,传递给SidebarItem
  const isTailLinkPage = pathname === '/taillink';

  return (
    <ul className="flex-1 px-3">
      <motion.div {...item_icon_motion_props}>
        <SidebarItem
          buttonIcon={CalendarPlus}
          isExpanded={isExpanded}
          buttonRoute="/taillink"
          buttonText="TailLink"
          isCurrentPage={isTailLinkPage}
        />
      </motion.div>
    </ul>
  );
}

3. 给图标组件本身加上memo(可选但推荐)

如果你的CalendarPlus是自定义组件,记得用React.memo包裹它,双重保险:

// 比如你的CalendarPlus组件
import { memo } from 'react';

const CalendarPlus = memo((props) => {
  console.log('CalendarPlus渲染了'); // 可以用这个日志测试是否还重渲染
  return <svg {...props}>{/* 图标内容 */}</svg>;
});

export default CalendarPlus;

最后验证

做完这些修改后,你可以在图标组件里加个console.log,切换侧边栏展开/收起时,应该不会再打印日志了,图标也不会有可见的重渲染闪烁。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 13:35:26