React中通过Props初始化组件State时状态卡0无法更新的解决方法
问题背景
现有一个图片预览Modal组件,接收图片源数据集、初始展示索引作为入参,组件两侧配置导航箭头,支持递增/递减切换当前展示的图片索引,组件初始简化实现代码如下:
function Modal({ index, data, onClose, show }) { const [currIndex, setCurrIndex] = useState(index); return ( <main className={show ? "Modal Open" : "Modal"}> <img src={data[currIndex].link} alt="ModalImage" /> <IconContext.Provider value={{ className: "Arrows" }} > {currIndex < data.length - 1 && (<AiOutlineRight className="ArrowNext" onClick={() => setCurrIndex(currIndex + 1)} />)} {currIndex > 0 && (<AiOutlineLeft className="ArrowLast" onClick={setCurrIndex(currIndex - 1)} />)} </IconContext.Provider> <div className="ModalBackground" onClick={onClose} /> </main> ) } export default Modal;
父组件中按如下方式引入使用该组件:
<Modal show={show} onClose={() => setShow(false)} data={filterData()} index={modalIndex} ></Modal>
当前组件运行存在异常:内部currIndex状态初始值始终为0,点击导航箭头执行增减操作时状态无法正常更新,会被立即重置为0,始终卡在0值。经排查Props传值逻辑正常,仅基于Props设置State初始值的逻辑未正常生效,需要实现基于父组件传入的选中值正确设置Modal内部初始状态的效果。
故障原因
两个问题共同导致了该异常:
- React中
useState的初始值仅在组件首次挂载时生效一次,后续组件重渲染时,不会自动使用新传入的Props更新内部状态。你的Modal组件默认在show=false时就已经挂载在父组件中,首次挂载时父组件传入的modalIndex初始值通常为0;后续点击图片触发Modal打开、更新modalIndex为目标值时,Modal组件仅触发重渲染,不会重新执行useState的初始化逻辑,因此currIndex会一直停留在首次挂载拿到的0值。 - 左箭头的点击事件写法错误:
onClick={setCurrIndex(currIndex - 1)}会在组件每次渲染时直接执行setCurrIndex,触发无限重渲染,进一步导致状态更新异常。
修复方案
根据业务场景选择以下任意一种方案即可:
方案1:监听Props变化同步内部状态
如果需要在Modal内部维护独立的索引状态,可以通过useEffect监听打开状态、传入索引的变化,在Modal打开时同步内部状态为传入的初始值,同时修正左箭头的事件写法:
import { useState, useEffect } from 'react'; function Modal({ index, data, onClose, show }) { const [currIndex, setCurrIndex] = useState(index); // 监听Modal打开状态和传入index的变化,打开时同步内部状态 useEffect(() => { if (show) { setCurrIndex(index); } }, [show, index]); return ( <main className={show ? "Modal Open" : "Modal"}> <img src={data[currIndex].link} alt="ModalImage" /> <IconContext.Provider value={{ className: "Arrows" }} > {currIndex < data.length - 1 && (<AiOutlineRight className="ArrowNext" onClick={() => setCurrIndex(currIndex + 1)} />)} {/* 修正点击事件写法,用箭头函数包裹触发逻辑 */} {currIndex > 0 && (<AiOutlineLeft className="ArrowLast" onClick={() => setCurrIndex(currIndex - 1)} />)} </IconContext.Provider> <div className="ModalBackground" onClick={onClose} /> </main> ) } export default Modal;
方案2:改为完全受控组件(推荐)
如果不需要在Modal内部维护独立的索引状态,更稳妥的方式是将索引状态完全交给父组件管理,Modal仅负责触发更新事件,从根源避免Props和内部状态不同步的问题:
- 父组件维护当前展示的索引状态,将当前索引、索引更新方法作为Props传入Modal
- Modal内部不再保存
currIndex状态,切换图片时直接调用父组件传入的更新方法即可
如果你的Modal不需要保留关闭前的浏览位置,也可以直接用条件渲染控制Modal挂载:{show && <Modal .../>},这样每次打开Modal都是重新挂载,useState会自动读取最新的index作为初始值,注意这种方式需要提前处理好Modal入场动画的兼容问题。
内容的提问来源于stack exchange,提问作者Sean Cesario
相关产品推荐
相关产品推荐

