Next.js页面重载与状态变更后组件渲染异常问题
Next.js页面重载时组件异常嵌套渲染问题
我在开发Next.js项目时遇到了一个棘手的问题:每次重载页面,都会出现组件异常嵌套渲染的情况——RoomCards组件近乎以自身子组件的形式重复渲染,演示效果为页面重载后,原本的RoomCards容器内部又多出一套完整的RoomCards结构。
相关代码
父组件渲染逻辑
<Col md={9} className={styles.Main}> {roomsError && <p>{t('common:DATA_LOADING_ERROR_TEXT')}</p>} {roomsData.length > 0 && <RoomCards user={user} roomsData={roomsData} />} {roomsData.length === 0 && !loading && <RoomCardEmpty/>} {loading && <p>{t('ROOMS_LOADING_ROOMS')}</p>} </Col>
状态定义
const [roomsData, setRoomsData] = useSessionStorage('dorms', []); const [hasMore, setHasMore] = useState(true); const [loading, setLoading] = useState(false); const [roomsError, setRoomsError] = useState(false);
样式代码
.Main { overflow-x: hidden; padding-bottom: 20px; }
RoomCards组件代码
export default function RoomCards({ roomsData = [], user = null, uploadsPage = false, className = '', style, ...restProps }) { const {t} = useTranslation('rooms'); const rowClassName = uploadsPage ? 'row-cols-1 row-cols-md-2' : ''; const colMd = uploadsPage ? null : 10; if (roomsData.length === 0) { return ( <> {uploadsPage && <p>{t('ROOMS_CARDS_NO_UPLOADED_ROOMS_TEXT')}</p>} {!uploadsPage && <p>{t('ROOMS_CARDS_NO_ROOMS_TEXT')}</p>} </> ); } else { return ( <Row className={`justify-content-center g-5 ${rowClassName}`} style={uploadsPage ? {paddingBottom: '80px'} : {}}> {roomsData.map((roomData, index) => ( <Col key={index} md={colMd} className={className} style={style} {...restProps} > {roomData.external ? <ExternalRoomCard user={user} roomData={roomData} uploadsPage={uploadsPage} /> : <RoomCard user={user} roomData={roomData} uploadsPage={uploadsPage} />} </Col> ))} </Row> ); } }
渲染后HTML对比
- 重载前:HTML结构正常,RoomCards组件仅渲染一次,内部是对应的RoomCard/ExternalRoomCard子组件
- 重载后:RoomCards组件内部嵌套了另一层完整的RoomCards结构,出现重复渲染
补充异常说明
再次测试后异常仍存在,相关截图显示:
- 组件层级出现多层嵌套,RoomCards被重复包裹
- DOM结构中出现重复的RoomCards容器,导致布局混乱
问题定位
- key值使用不当:遍历roomsData时用
index作为key,页面重载后sessionStorage数据变化时,React无法正确识别组件身份,引发复用错误和重复渲染。 - SSR与sessionStorage冲突:Next.js服务端渲染时无法读取浏览器sessionStorage,useSessionStorage默认返回空数组;客户端hydrate时读取存储数据,状态同步不及时导致二次渲染异常。
- props传递冗余:RoomCards将
...restProps传递给内部Col组件,若父组件传递的属性包含触发渲染的字段,可能引发意外渲染。
解决方案
- 替换唯一key值:把遍历的
key={index}改为roomData的唯一标识(如roomData.id),确保React能准确区分子组件:
<Col key={roomData.id} md={colMd} className={className} style={style} >
- 兼容SSR与sessionStorage:用
useState+useEffect替代useSessionStorage,避免服务端与客户端状态不一致:
const [roomsData, setRoomsData] = useState([]); // 客户端读取sessionStorage useEffect(() => { const storedData = sessionStorage.getItem('dorms'); if (storedData) { setRoomsData(JSON.parse(storedData)); } }, []); // 同步状态到sessionStorage useEffect(() => { sessionStorage.setItem('dorms', JSON.stringify(roomsData)); }, [roomsData]);
精简props传递:移除不必要的
...restProps,只传递Col组件需要的属性,避免意外渲染触发。优化条件渲染逻辑:用嵌套三元表达式替代独立
&&判断,避免多条件同时满足导致的重复渲染:
<Col md={9} className={styles.Main}> {roomsError && <p>{t('common:DATA_LOADING_ERROR_TEXT')}</p>} {loading ? ( <p>{t('ROOMS_LOADING_ROOMS')}</p> ) : roomsData.length > 0 ? ( <RoomCards user={user} roomsData={roomsData} /> ) : ( <RoomCardEmpty/> )} </Col>
内容的提问来源于stack exchange,提问作者rowan-vr
相关产品推荐
相关产品推荐

