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

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不匹配等,这些错误会导致数据处理异常,间接影响组件更新。

修复步骤

  1. 将循环内的Hook调用抽为独立组件
    创建单独的组件封装Hook调用,避免在循环中直接使用Hook:

    const ExternalItemData = ({ curEle }) => {
      const [data, loading, error] = useDocumentData(curEle.data().ref);
      return { data, meta: curEle, loading };
    };
    
  2. 用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} />
          : <></>}
        </>
      );
    };
    
  3. 修正笔误与未定义变量

    • 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} />
        </>
      )
      
  4. 优化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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 23:17:28