React状态变更未触发数组Map重渲染及计数异常问题
问题分析与解决方案
核心问题
- 每个
LogoAnim独立维护isVisible状态,父组件randNumber无法准确同步实际显示的元素数量,导致最后一个元素删除时无法触发重置逻辑。 - 更新
randNumber时依赖旧状态值(randNumber -1),存在状态更新竞态,会导致计数错误。
修复方案
- 将可见性状态统一移到父组件管理:父组件
LogoStack维护包含所有logo状态的数组,准确跟踪每个元素的可见性,避免状态分散导致的不同步。 - 使用函数式状态更新:确保每次状态更新都基于最新的状态值,避免竞态问题。
- 实时统计可见元素数量:父组件监听可见元素数量变化,当数量为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
相关产品推荐
相关产品推荐

