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

React中useEffect获取数据后状态丢失问题求助

React状态重置问题:从Google Sheets获取数据后artistNamesArray被清空

问题描述

在PageContent.jsx的useEffect中成功从Google Sheets获取数据,将artistNamesArray状态设置为响应值后传递给子组件BandsColumn,但该状态会莫名重置为空数组,导致子组件无法获取对应数据。刚接触React,希望了解重渲染机制,明确状态重置的原因及解决方法。

原代码

PageContent父组件

const PageContent = () => {
  let allArtistNamesArr = [];
  const [state, setState] = useState({
    sheetData: [],
    artistNamesArray: [],
    // yearsArray: []
  });

  useEffect(() => {
    const fetchData = async () => {
      let response = await callGoogleSheets();
      setState({ ...state, sheetData: response });
    };
    fetchData();
  }, []);

  useEffect(() => {
    const getAllBandNames = async () => {
      state.sheetData.forEach((artist) => {
        allArtistNamesArr.push(artist.Band);
      });

      let uniqueArtistNameSet = new Set(allArtistNamesArr);
      let uniqueArtistNameArr = Array.from(uniqueArtistNameSet);
      if (!state.artistNamesArray) 
      setState({ ...state, artistNamesArray: uniqueArtistNameArr });
    };

    getAllBandNames();
  }, [state.sheetData]);

  return (
    <section id="content">
      <div id="dataContainer">
        <BandsColumn
          bandNames={state.artistNamesArray}
          sheetData={state.sheetData}
        ></BandsColumn>
        <YearsColumn></YearsColumn>
        <ShowsColumn></ShowsColumn>
        <RecordingDetailsColumn></RecordingDetailsColumn>
      </div>
    </section>
  );
};

BandsColumn子组件

const BandsColumn = ({ bandNames, sheetData }) => {
    const [state,setState] = useState({
        allBandNames: []
    })

  useEffect(() => {
    console.log("bandNames: " + bandNames)
  }, [bandNames]);

  const getSpecificArtist = (event) => {
    let specificArtistArray = []
    let clickedArtist = event.target.textContent
    
    sheetData.forEach((artist) => {
        if (clickedArtist === artist.Band) {
            specificArtistArray.push(artist)
        }
    })
    console.log(specificArtistArray)
    
  }

  return (
    <aside id="bandContainer">
      <h1>Bands: </h1>
      <ul>
        {state.allBandNames.length && state.allBandNames.map((name, index) => (
          <li key={index}>
            <a onClick={getSpecificArtist}>{name}</a>
          </li>
        ))}
      </ul>
    </aside>
  );
};

问题原因

  1. 状态更新的闭包陷阱:使用setState({ ...state, ... })时,依赖的是闭包捕获的旧状态。若组件在状态更新期间发生重渲染,可能会使用过期的state值,导致状态更新被覆盖或异常。
  2. 顶层变量的重复初始化:allArtistNamesArr是组件顶层变量,每次组件重渲染都会被重新声明为空数组,无法跨渲染周期保存数据,可能导致数据收集异常。
  3. 子组件未正确使用Props:子组件接收了bandNames但未将其同步到内部的allBandNames状态,渲染时始终使用空数组,导致列表无法显示。
  4. 不必要的异步标记:第二个useEffect中的getAllBandNames函数被标记为async,但内部没有异步操作,可能引发不必要的执行时序问题。

解决方法

1. 使用函数式状态更新

通过setState(prevState => {...})获取最新状态,避免闭包带来的旧状态问题。

2. 避免跨渲染的顶层变量

将数据处理的临时变量移到useEffect内部,确保每次处理都基于当前的sheetData重新生成。

3. 子组件直接使用Props或同步状态

移除子组件中不必要的内部状态,直接使用父组件传递的bandNames;若需子组件维护状态,在useEffect中同步Props值。

4. 移除无意义的异步标记

去掉getAllBandNames的async标记,避免执行时序混乱。

修正后的代码

PageContent父组件

const PageContent = () => {
  const [state, setState] = useState({
    sheetData: [],
    artistNamesArray: [],
    // yearsArray: []
  });

  useEffect(() => {
    const fetchData = async () => {
      const response = await callGoogleSheets();
      // 函数式更新,确保基于最新状态
      setState(prevState => ({ ...prevState, sheetData: response }));
    };
    fetchData();
  }, []);

  useEffect(() => {
    const getAllBandNames = () => {
      // 临时变量移到内部,每次处理重新创建
      const allArtistNamesArr = [];
      state.sheetData.forEach((artist) => {
        allArtistNamesArr.push(artist.Band);
      });

      const uniqueArtistNameSet = new Set(allArtistNamesArr);
      const uniqueArtistNameArr = Array.from(uniqueArtistNameSet);
      // 函数式更新状态
      setState(prevState => ({ ...prevState, artistNamesArray: uniqueArtistNameArr }));
    };

    // 仅当sheetData有数据时执行处理
    if (state.sheetData.length > 0) {
      getAllBandNames();
    }
  }, [state.sheetData]);

  return (
    <section id="content">
      <div id="dataContainer">
        <BandsColumn
          bandNames={state.artistNamesArray}
          sheetData={state.sheetData}
        />
        <YearsColumn />
        <ShowsColumn />
        <RecordingDetailsColumn />
      </div>
    </section>
  );
};

BandsColumn子组件

const BandsColumn = ({ bandNames, sheetData }) => {
  // 移除不必要的内部状态,直接使用Props
  useEffect(() => {
    console.log("bandNames: ", bandNames);
  }, [bandNames]);

  const getSpecificArtist = (event) => {
    const clickedArtist = event.target.textContent;
    // 使用filter简化代码
    const specificArtistArray = sheetData.filter(artist => artist.Band === clickedArtist);
    console.log(specificArtistArray);
  };

  return (
    <aside id="bandContainer">
      <h1>Bands: </h1>
      <ul>
        {bandNames.length > 0 && bandNames.map((name, index) => (
          <li key={index}>
            <a onClick={getSpecificArtist}>{name}</a>
          </li>
        ))}
      </ul>
    </aside>
  );
};

React重渲染机制说明

React组件会在以下场景触发重渲染:

  • 组件自身的state发生变化;
  • 组件接收的props发生变化;
  • 父组件触发重渲染(默认子组件会跟随重渲染,可通过React.memo优化);
  • 调用forceUpdate方法(不推荐使用)。

每次重渲染时,组件函数会重新执行,所有顶层变量都会被重新初始化。因此,不能依赖顶层变量保存跨渲染周期的数据,必须使用useState或useRef来持久化状态。

内容的提问来源于stack exchange,提问作者TuckerLarrabee

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 17:15:59