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

React中避免状态变更时CSS动画重复触发的问题

解决React画廊组件因重渲染重置fade-in动画的问题

问题根源

点击画廊图片更新selectedIndex和galleryVisible状态时,父组件触发重渲染,导致画廊子组件也跟着重新渲染,进而重置了初始的fade-in动画。你尝试的useMemo主要用于缓存计算值,无法直接阻止组件本身的重渲染,因此没解决问题。

可行解决方案

1. 用React.memo隔离画廊组件的重渲染

React.memo会对组件的props进行浅比较,只有当props发生变化时才重新渲染组件。结合useCallback稳定回调函数的引用,避免不必要的重渲染:

// 画廊组件用React.memo包裹
const Gallery = React.memo(({ images, onImageClick }) => {
  return (
    <div className="gallery" style={{ animation: 'fadeIn 0.5s ease-in-out forwards' }}>
      {images.map((img, idx) => (
        <img
          key={idx}
          src={img}
          onClick={() => onImageClick(idx)}
          alt={`Gallery img ${idx}`}
        />
      ))}
    </div>
  );
});

// 父组件中用useCallback稳定点击回调
const Parent = () => {
  const [selectedIndex, setSelectedIndex] = useState(null);
  const [galleryVisible, setGalleryVisible] = useState(false);

  // 用useCallback缓存回调,避免每次父组件重渲染创建新函数
  const handleImageClick = useCallback((idx) => {
    setSelectedIndex(idx);
    setGalleryVisible(true);
  }, []);

  const images = ['https://picsum.photos/200/300?random=1', 'https://picsum.photos/200/300?random=2', 'https://picsum.photos/200/300?random=3'];

  return (
    <div>
      <Gallery images={images} onImageClick={handleImageClick} />
      {galleryVisible && (
        <div className="fullscreen-modal">
          <img src={images[selectedIndex]} alt="Fullscreen" />
          <button onClick={() => setGalleryVisible(false)}>关闭</button>
        </div>
      )}
    </div>
  );
};

2. 改用CSS类+Ref控制动画,避免重渲染触发重置

如果希望动画仅在组件首次挂载时触发,后续重渲染不再重置,可以通过Ref手动添加动画类,而非依赖组件初始渲染的CSS:

const Gallery = React.memo(({ images, onImageClick }) => {
  const galleryRef = useRef(null);

  useEffect(() => {
    // 组件首次挂载时添加动画类
    galleryRef.current?.classList.add('fade-in');
  }, []);

  return (
    <div ref={galleryRef} className="gallery">
      {images.map((img, idx) => (
        <img
          key={idx}
          src={img}
          onClick={() => onImageClick(idx)}
          alt={`Gallery img ${idx}`}
        />
      ))}
    </div>
  );
});

对应的CSS:

.gallery {
  opacity: 0;
  transition: opacity 0.5s ease-in-out;
}

.gallery.fade-in {
  opacity: 1;
}

这种方式下,动画仅在组件挂载时触发一次,后续重渲染不会移除已添加的类,自然不会重置动画。

3. 拆分状态,缩小重渲染范围

将控制全屏组件的状态(selectedIndex、galleryVisible)封装到单独的组件中,让画廊组件所在的部分不受到这些状态更新的影响:

// 封装全屏组件及相关状态
const FullscreenViewer = ({ images }) => {
  const [selectedIndex, setSelectedIndex] = useState(null);
  const [isVisible, setIsVisible] = useState(false);

  return (
    <>
      {/* 把触发全屏的回调传给画廊 */}
      <Gallery images={images} onImageClick={(idx) => {
        setSelectedIndex(idx);
        setIsVisible(true);
      }} />
      {isVisible && (
        <div className="fullscreen-modal">
          <img src={images[selectedIndex]} alt="Fullscreen" />
          <button onClick={() => setIsVisible(false)}>关闭</button>
        </div>
      )}
    </>
  );
};

// 父组件不再持有全屏相关状态,避免重渲染影响画廊
const Parent = () => {
  const images = ['https://picsum.photos/200/300?random=1', 'https://picsum.photos/200/300?random=2', 'https://picsum.photos/200/300?random=3'];
  
  return <FullscreenViewer images={images} />;
};

内容的提问来源于stack exchange,提问作者Keyle

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 14:31:02