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

React状态变更未触发数组Map重渲染及计数异常问题

问题分析与解决方案

核心问题

  • 每个LogoAnim独立维护isVisible状态,父组件randNumber无法准确同步实际显示的元素数量,导致最后一个元素删除时无法触发重置逻辑。
  • 更新randNumber时依赖旧状态值(randNumber -1),存在状态更新竞态,会导致计数错误。

修复方案

  1. 将可见性状态统一移到父组件管理:父组件LogoStack维护包含所有logo状态的数组,准确跟踪每个元素的可见性,避免状态分散导致的不同步。
  2. 使用函数式状态更新:确保每次状态更新都基于最新的状态值,避免竞态问题。
  3. 实时统计可见元素数量:父组件监听可见元素数量变化,当数量为0时自动重置生成新的logo数组。

修改后的LogoStack组件

const LogoStack = ({ hideTablet }: { hideTablet?: boolean }) => {
  // 用数组维护每个logo的可见状态,每个元素包含唯一id和isVisible
  const [logoList, setLogoList] = useState<Array<{ id: number; isVisible: boolean }>>([]);

  // 初始化或重置logo列表
  const resetLogoList = () => {
    const count = getRandomInt(10, 30);
    setLogoList(Array.from({ length: count }, (_, idx) => ({
      id: idx,
      isVisible: true
    })));
  };

  // 初始化调用
  useEffect(() => {
    resetLogoList();
  }, []);

  // 监听可见元素数量,为0时重置
  useEffect(() => {
    const visibleCount = logoList.filter(item => item.isVisible).length;
    if (visibleCount === 0) {
      resetLogoList();
    }
  }, [logoList]);

  // 处理单个logo的隐藏事件
  const handleLogoHide = (id: number) => {
    setLogoList(prevList => 
      prevList.map(item => 
        item.id === id ? { ...item, isVisible: false } : item
      )
    );
  };

  return (
    <div
      className={`col-span-2 flex-col-reverse justify-start ${hideTablet ? 'hidden lg:flex' : 'flex'}`}
    >
      {logoList.map((logo) => {
        return logo.isVisible ? (
          <LogoAnim
            key={logo.id}
            id={logo.id}
            onHide={handleLogoHide}
          />
        ) : null;
      })}
    </div>
  );
};

修改后的LogoAnim组件

const LogoAnim = ({ id, onHide }: { id: number; onHide: (id: number) => void }) => {
  // 不再维护独立的isVisible状态,由父组件控制渲染

  return (
    <AnimatePresence mode="sync">
      <motion.div
        key={`logo-${id}`}
        className={`logo cursor-pointer overflow-hidden`}
        initial={'visible'}
        animate={'visible'}
        exit={'hidden'}
        variants={variants}
        transition={{
          duration: 0.4,
          delay: 0,
        }}
        onMouseEnter={() => {
          // 通知父组件隐藏当前logo
          onHide(id);
        }}
      >
        <motion.div
          key={`text-anim-${id}`}
          initial={'hidden'}
          animate={'visible'}
          exit={'hidden'}
          variants={variants}
          transition={{
            duration: 0.3,
            delay: (id * 100) / 1000,
          }}
        >
          <motion.div
            key={`text-inner-${id}`}
            initial={'visible'}
            exit={'hidden'}
            variants={textVariants}
            transition={{
              duration: 0,
              delay: 0.4,
            }}
          >
            <MainLogo />
          </motion.div>
        </motion.div>
      </motion.div>
    </AnimatePresence>
  );
};

关键修改说明

  • LogoStack用logoList数组替代randNumber,直接跟踪每个logo的可见状态,确保计数准确。
  • resetLogoList函数统一处理初始化和重置逻辑,生成随机数量的logo状态数组。
  • handleLogoHide使用函数式更新修改状态,避免依赖旧值导致的竞态问题。
  • 新增的useEffect实时统计可见元素数量,为0时自动触发重置。
  • LogoAnim移除独立的isVisible状态,仅负责渲染动画和触发隐藏事件,状态完全由父组件管控,消除了状态不同步问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 17:35:11