React组件未更新:MapWrap组件无限重渲染问题求助
问题分析与修复方案
核心问题根源
- 无限重渲染循环:FeedWraper组件每次渲染时,都会通过
externalRaw.docs.map(...)生成全新的externalData数组对象——即使数组内容没变,引用也会更新。这导致Feed组件中useEffect的依赖externalData每次都变化,触发useEffect执行setMarkerData,进而引发Dashboard重渲染,FeedWraper再次生成新数组,形成循环。 - 违反React Hook规则:在
map循环中直接调用useDocumentData,React不允许在循环、条件语句内调用Hook,会导致Hook调用顺序混乱,引发不可预测的渲染行为。 - 多处笔误与未定义变量:比如Feed组件中使用未定义的
internalRaw、FeedWraper返回的element未赋值、Feed渲染的InviteList与定义的ExternalList不匹配等,这些错误会导致数据处理异常,间接影响组件更新。
修复步骤
将循环内的Hook调用抽为独立组件
创建单独的组件封装Hook调用,避免在循环中直接使用Hook:const ExternalItemData = ({ curEle }) => { const [data, loading, error] = useDocumentData(curEle.data().ref); return { data, meta: curEle, loading }; };用useMemo缓存externalData数组
在FeedWraper中使用useMemo缓存数组,只有当externalRaw.docs变化时才重新生成,避免每次渲染创建新引用:const FeedWraper = ({ setMarkerData, internalData, externalRaw }) => { const externalData = useMemo(() => { return externalRaw.docs.map((curEle, idx) => { const [data, loading] = useDocumentData(curEle.data().ref); // 修正:给element赋值,这里假设需要curEle对象,可根据实际需求调整 return { data, element: curEle, loading, meta: curEle, idx }; }); }, [externalRaw.docs]); // 修正:判断加载状态需要遍历数组项 const isExternalLoading = externalData.some(item => item.loading); return ( <> {!isExternalLoading ? <Feed internalData={internalData} externalData={externalData} setMarkerData={setMarkerData} /> : <></>} </> ); };修正笔误与未定义变量
- Feed组件中把
internalRaw改为props传入的internalData:useEffect(() => { let tmpArr = []; // 修正:internalRaw → internalData internalData.docs.forEach((element) => { tmpArr.push({ position: { lng: parseFloat(element.data().longitude), lat: parseFloat(element.data().latitude), }, }); }); // ... 其余代码 }, [internalData, externalData]); - Feed中把
InviteList改为定义好的ExternalList:return( <> <div> { internalData.docs.length > 0 ? ( internalData.docs.map((tmp, idx) => <InternalCardElement key={idx} id={tmp.id} raw={tmp.data()} />) ) : ( <></> )} </div> {/* 修正:InviteList → ExternalList */} <ExternalList externalData={externalData} /> </> )
- Feed组件中把
优化useEffect避免不必要的setState
添加数组浅比较,只有当数据真的变化时才更新状态:// 可自行实现浅比较,或引入lodash的isEqual const isArrayEqual = (arr1, arr2) => { if (arr1.length !== arr2.length) return false; return arr1.every((item, idx) => item.position.lng === arr2[idx].position.lng && item.position.lat === arr2[idx].position.lat ); }; useEffect(() => { let tmpArr = []; internalData.docs.forEach((element) => { tmpArr.push({ position: { lng: parseFloat(element.data().longitude), lat: parseFloat(element.data().latitude), }, }); }); externalData.forEach(({ element }) => { if (element) { tmpArr.push({ position: { lng: parseFloat(element.data().longitude), lat: parseFloat(element.data().latitude), }, }); } }); // 只有数组内容变化时才更新 if (!isArrayEqual(tmpArr, markerData)) { setMarkerData(tmpArr); } }, [internalData, externalData, markerData]);
修改后的完整代码示例
import { useState, useEffect, useMemo } from 'react'; const ExternalList = ({ externalData }) => { return( <div> {externalData.map(({data, meta, idx}) => ( <div key={idx}> {data != undefined ? <ExternalCardElement key={idx} raw={data} meta={meta} /> : <></>} </div> ))} </div> ); }; const Feed = ({ internalData, setMarkerData, externalData, markerData }) => { // 自定义数组浅比较函数 const isArrayEqual = (arr1, arr2) => { if (arr1.length !== arr2.length) return false; return arr1.every((item, idx) => item.position.lng === arr2[idx].position.lng && item.position.lat === arr2[idx].position.lat ); }; useEffect(() => { let tmpArr = []; internalData.docs.forEach((element) => { tmpArr.push({ position: { lng: parseFloat(element.data().longitude), lat: parseFloat(element.data().latitude), }, }); }); externalData.forEach(({ element }) => { if (element) { tmpArr.push({ position: { lng: parseFloat(element.data().longitude), lat: parseFloat(element.data().latitude), }, }); } }); if (!isArrayEqual(tmpArr, markerData)) { setMarkerData(tmpArr); } }, [internalData, externalData, markerData]); return( <> <div> { internalData.docs.length > 0 ? ( internalData.docs.map((tmp, idx) => <InternalCardElement key={idx} id={tmp.id} raw={tmp.data()} />) ) : ( <></> )} </div> <ExternalList externalData={externalData} /> </> ); }; const FeedWraper = ({ setMarkerData, internalData, externalRaw }) => { const externalData = useMemo(() => { return externalRaw.docs.map((curEle, idx) => { const [data, loading] = useDocumentData(curEle.data().ref); return { data, element: curEle, loading, meta: curEle, idx }; }); }, [externalRaw.docs]); const isExternalLoading = externalData.some(item => item.loading); return( <> {!isExternalLoading ? <Feed internalData={internalData} externalData={externalData} setMarkerData={setMarkerData} /> : <></>} </> ); }; export default function Dashboard() { const [markerData, setMarkerData] = useState([]); const [internalData, internalLoading, error] = useCollection(ineternalQuery); const [externalRaw, externalRawLoading, baderror] = useCollection(externalQuery); return ( <div> <div> {!internalLoading && !externalRawLoading ? <FeedWraper internalData={internalData} externalRaw={externalRaw} setMarkerData={setMarkerData} /> : <></>} </div> <div> <MapWrap markerData={markerData} defaultLocation={false} /> </div> </div> ); }
内容的提问来源于stack exchange,提问作者Orion447
相关产品推荐
相关产品推荐

